use super::{
PICKER_MODAL, draw_modal_panel_with_hotkeys, 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, Wrap},
};
fn split_body(body: Rect) -> (Rect, Rect) {
let list_height = (body.height / 3).max(1).min(body.height);
(
Rect::new(body.x, body.y, body.width, list_height),
Rect::new(
body.x,
body.y + list_height,
body.width,
body.height - list_height,
),
)
}
pub(crate) fn mcp_modal_list_area(area: Rect) -> Option<Rect> {
picker_modal_list_area(area, "MCP Servers", 1).map(|body| split_body(body).0)
}
pub(crate) fn mcp_definition_area(area: Rect) -> Option<Rect> {
picker_modal_list_area(area, "MCP Servers", 1).map(|body| split_body(body).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_with_hotkeys(
frame,
area,
"MCP Servers",
PICKER_MODAL,
theme,
"[↑/↓] server • [PgUp/PgDn] inspect • [Enter] approve/revoke",
Some("[Esc] close"),
) else {
return;
};
if a.height == 0 {
return;
}
frame.render_widget(
Paragraph::new("Approvals apply next launch; inspect current definition below.")
.style(theme.muted(false)),
Rect::new(a.x, a.y, a.width, 1),
);
let Some(body) = 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()),
body,
);
return;
}
let (list_area, definition_area) = split_body(body);
let items = m
.rows
.iter()
.map(|r| {
ListItem::new(Line::from(format!(
"{} {} ({}): {}",
if r.enabled { "[x]" } else { "[ ]" },
r.name,
r.kind,
r.snapshot.status
)))
})
.collect::<Vec<_>>();
render_picker_list(
frame,
list_area,
items,
m.selected,
&state.scroll_views.mcp_modal,
theme,
);
if let Some(row) = m.rows.get(m.selected) {
let paragraph = Paragraph::new(row.definition_display.as_str())
.wrap(Wrap { trim: false })
.style(theme.pane());
let max = paragraph
.line_count(definition_area.width.max(1))
.saturating_sub(usize::from(definition_area.height));
let offset = usize::from(m.definition_scroll)
.min(max)
.min(usize::from(u16::MAX)) as u16;
frame.render_widget(paragraph.scroll((offset, 0)), definition_area);
}
}