fix(panels): host-managed mcp card shows the embed port with no start/stop

This commit is contained in:
Kayshen-X 2026-07-18 16:19:15 +08:00
parent b35f90670b
commit c54f5c8cb9
2 changed files with 64 additions and 7 deletions

View file

@ -111,6 +111,27 @@ impl McpServer {
}
impl AgentSettings {
/// True when an embedding host owns the MCP lifecycle
/// (`embed_mcp_url` set): the server card then reads as
/// always-running at the host's port, with no start/stop button
/// and no port editing — the daemon-internal `mcp_server` state
/// is meaningless in that surface.
pub fn mcp_host_managed(&self) -> bool {
self.embed_mcp_url.is_some()
}
/// The port component of `embed_mcp_url`, for the read-only port
/// display (e.g. `"63655"` from `http://127.0.0.1:63655/mcp`).
pub fn embed_mcp_port_text(&self) -> Option<&str> {
let url = self.embed_mcp_url.as_deref()?;
let after_scheme = url.split_once("://").map_or(url, |(_, rest)| rest);
let authority = after_scheme
.split(['/', '?', '#'])
.next()
.unwrap_or_default();
authority.rsplit_once(':').map(|(_, port)| port)
}
/// The client-config card's one-line display text. Prefers the
/// embedding host's real endpoint (`embed_mcp_url`, set via the
/// bridge init) over the daemon-internal `mcp_server` port.
@ -346,3 +367,18 @@ mod embed_mcp_url_tests {
.contains("\"url\": \"http://127.0.0.1:63655/mcp\""));
}
}
#[cfg(test)]
mod embed_mcp_host_tests {
use super::*;
#[test]
fn host_managed_state_and_port_derive_from_the_embed_url() {
let mut settings = AgentSettings::default();
assert!(!settings.mcp_host_managed());
assert_eq!(settings.embed_mcp_port_text(), None);
settings.embed_mcp_url = Some("http://127.0.0.1:63655/mcp".into());
assert!(settings.mcp_host_managed());
assert_eq!(settings.embed_mcp_port_text(), Some("63655"));
}
}

View file

@ -45,7 +45,7 @@ fn server_card_top(content: Rect) -> f32 {
}
fn client_config_block_h(settings: &AgentSettings) -> f32 {
if settings.mcp_server.running {
if settings.mcp_host_managed() || settings.mcp_server.running {
CLIENT_CONFIG_GAP + CLIENT_CONFIG_H
} else {
0.0
@ -156,13 +156,21 @@ fn client_config_copy_button_rect(content: Rect) -> Rect {
pub(super) const CLI_INTEGRATIONS_AVAILABLE: bool = !cfg!(target_arch = "wasm32");
pub fn hit_test(content: Rect, settings: &AgentSettings, scrolled: Point2D) -> McpHit {
if (server_button_rect(content)).contains(scrolled) {
// Host-managed (embed): the extension owns the lifecycle — no
// start/stop, no port editing; the config card always copies.
let host_managed = settings.mcp_host_managed();
if !host_managed && (server_button_rect(content)).contains(scrolled) {
return McpHit::ToggleServer;
}
if settings.mcp_server.running && (client_config_copy_button_rect(content)).contains(scrolled) {
if (host_managed || settings.mcp_server.running)
&& (client_config_copy_button_rect(content)).contains(scrolled)
{
return McpHit::CopyClientConfig;
}
if !settings.mcp_server.running && (port_field_rect(content)).contains(scrolled) {
if !host_managed
&& !settings.mcp_server.running
&& (port_field_rect(content)).contains(scrolled)
{
return McpHit::FocusPort;
}
if CLI_INTEGRATIONS_AVAILABLE {
@ -255,7 +263,10 @@ fn paint_server_card(
radius: 10.0,
}
.paint(cx.backend, card, &tokens_from_theme(theme));
let running = settings.mcp_server.running;
// Host-managed (embed): the extension's proxy is alive by the time any
// editor mounts — the card reads always-running at the host's port.
let host_managed = settings.mcp_host_managed();
let running = host_managed || settings.mcp_server.running;
let mid_y = card.origin.y + SERVER_CARD_H / 2.0;
let dot = Rect {
origin: Point2D::new(card.origin.x + 16.0, mid_y - 4.0),
@ -303,10 +314,15 @@ fn paint_server_card(
Point2D::new(port_field_x - 8.0 - port_label_w, mid_y + 4.0),
);
let port_field = port_field_rect(content);
let port_editable = !running;
let port_editable = !running && !host_managed;
let focused = port_editable && matches!(settings.focus, Some(SettingsFocus::McpPort));
let port_str = if focused {
ui.settings_input.text().to_owned()
} else if host_managed {
settings
.embed_mcp_port_text()
.unwrap_or_default()
.to_owned()
} else {
format!("{}", settings.mcp_server.port)
};
@ -349,6 +365,11 @@ fn paint_server_card(
.draw_text(&port_layout, Point2D::new(port_x, port_y));
}
// Host-managed: no start/stop affordance — the extension owns the
// lifecycle (hit_test skips ToggleServer under the same gate).
if host_managed {
return;
}
let btn_bg = if running { theme.muted } else { theme.primary };
let btn_fg = if running {
theme.foreground
@ -398,7 +419,7 @@ fn paint_client_config(
content: Rect,
now_ms: u64,
) {
if !settings.mcp_server.running {
if !settings.mcp_host_managed() && !settings.mcp_server.running {
return;
}
let rect = client_config_rect(content);