magi-code 0.80.0

Repository-aware CLI coding agent for terminal work
Documentation
use super::{
    PICKER_MODAL, draw_modal_panel, picker_list_area, picker_modal_list_area, render_picker_list,
};
use crate::tui::state::MissionControlState;
use ratatui::{
    Frame,
    layout::Rect,
    text::Line,
    widgets::{ListItem, Paragraph},
};
pub(crate) fn mcp_modal_list_area(area: Rect) -> Option<Rect> {
    picker_modal_list_area(area, "MCP Servers", 1)
}
pub(in crate::tui::render) fn draw_mcp_modal(
    frame: &mut Frame<'_>,
    area: Rect,
    state: &MissionControlState,
) {
    let Some(m) = &state.modals.mcp_modal else {
        return;
    };
    let theme = state.theme;
    let Some(a) = draw_modal_panel(frame, area, "MCP Servers", PICKER_MODAL, theme) else {
        return;
    };
    if a.height == 0 {
        return;
    }
    frame.render_widget(
        Paragraph::new(super::super::hotkey_line_with_theme(
            theme,
            "[Up]/[Down] • [Enter] toggle next launch • [Esc] close • default: off",
            theme.muted(false),
        ))
        .style(theme.muted(false)),
        Rect::new(a.x, a.y, a.width, 1),
    );
    let Some(list_area) = picker_list_area(a, 1) else {
        return;
    };
    if m.rows.is_empty() {
        frame.render_widget(
            Paragraph::new(vec![
                Line::from("No MCP servers configured."),
                Line::from("Add definitions to CONFIG_DIR/.mcp.json or cwd/.mcp.json"),
            ])
            .style(theme.pane()),
            list_area,
        );
        return;
    }
    let items = m
        .rows
        .iter()
        .map(|r| {
            ListItem::new(Line::from(format!(
                "{} {} ({})",
                if r.enabled { "[x]" } else { "[ ]" },
                r.name,
                r.kind
            )))
        })
        .collect::<Vec<_>>();
    render_picker_list(
        frame,
        list_area,
        items,
        m.selected,
        &state.scroll_views.mcp_modal,
        theme,
    );
}

#[cfg(test)]
mod tests {
    use super::*;
    use ratatui::{Terminal, backend::TestBackend};

    #[test]
    fn mcp_modal_shows_opt_in_and_next_launch_guidance() {
        let mut state = MissionControlState::default();
        state.open_mcp_modal(
            vec![crate::tui::state::McpServerToggleRow {
                name: "unapproved".into(),
                kind: "stdio",
                enabled: false,
            }],
            10,
        );
        let mut terminal = Terminal::new(TestBackend::new(100, 30)).unwrap();
        terminal
            .draw(|frame| draw_mcp_modal(frame, frame.area(), &state))
            .unwrap();
        let text: String = terminal
            .backend()
            .buffer()
            .content
            .iter()
            .map(|cell| cell.symbol())
            .collect();
        assert!(text.contains("default: off"));
        assert!(text.contains("toggle next launch"));
        assert!(text.contains("[ ] unapproved (stdio)"));
    }

    #[test]
    fn empty_mcp_modal_shows_definition_locations() {
        let mut state = MissionControlState::default();
        state.open_mcp_modal(vec![], 10);
        let mut terminal = Terminal::new(TestBackend::new(100, 30)).unwrap();
        terminal
            .draw(|frame| draw_mcp_modal(frame, frame.area(), &state))
            .unwrap();
        let text: String = terminal
            .backend()
            .buffer()
            .content
            .iter()
            .map(|cell| cell.symbol())
            .collect();
        assert!(text.contains("No MCP servers configured."));
        assert!(text.contains("CONFIG_DIR/.mcp.json or cwd/.mcp.json"));
    }
}