Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 36 additions & 1 deletion app/agent/tools/mcp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,13 @@ pub struct McpStatusRow {
/// Honest snapshot of MCP config + runtime state for the Settings UI.
pub struct McpStatusReport {
pub config_path_display: String,
/// Whether the user has an `mcp.json` with at least one server defined.
/// `false` means "no config yet" (missing file OR present-but-empty) — the
/// onboarding readiness step surfaces the setup prompt instead of a status
/// card. A present config that fails to load still counts as `true`: the
/// file exists, the user configured something, so we show the concrete error
/// rather than pretend nothing is there.
configured: bool,
rows: Vec<McpStatusRow>,
}

Expand All @@ -71,9 +78,23 @@ impl McpStatusReport {
&self.rows
}

fn single(config_path_display: String, label: &str, value: String, tone: McpRowTone) -> Self {
/// `true` when an `mcp.json` exists with at least one server defined (or when
/// a present config failed to load — the file is still there). `false` only
/// when there is no config yet: missing file or present-but-no-servers.
pub fn configured(&self) -> bool {
self.configured
}

fn single(
config_path_display: String,
configured: bool,
label: &str,
value: String,
tone: McpRowTone,
) -> Self {
Self {
config_path_display,
configured,
rows: vec![McpStatusRow {
label: label.to_string(),
value,
Expand All @@ -95,6 +116,7 @@ pub fn probe_mcp_status() -> McpStatusReport {
Err(error) => {
return McpStatusReport::single(
"unavailable".to_string(),
true,
"Status:",
format!("config path unavailable: {error}"),
McpRowTone::Bad,
Expand All @@ -110,6 +132,7 @@ fn probe_mcp_status_at(path: &Path) -> McpStatusReport {
if !path.exists() {
return McpStatusReport::single(
config_path_display,
false,
"Status:",
"no mcp.json (optional — MCP off)".to_string(),
McpRowTone::Neutral,
Expand All @@ -121,6 +144,7 @@ fn probe_mcp_status_at(path: &Path) -> McpStatusReport {
Err(error) => {
return McpStatusReport::single(
config_path_display,
true,
"Config error:",
anyhow_root_cause(&error),
McpRowTone::Bad,
Expand All @@ -131,6 +155,7 @@ fn probe_mcp_status_at(path: &Path) -> McpStatusReport {
if config.servers.is_empty() {
return McpStatusReport::single(
config_path_display,
false,
"Status:",
"config present, no servers defined".to_string(),
McpRowTone::Warn,
Expand Down Expand Up @@ -170,6 +195,7 @@ fn probe_mcp_status_at(path: &Path) -> McpStatusReport {

McpStatusReport {
config_path_display,
configured: true,
rows,
}
}
Expand Down Expand Up @@ -708,6 +734,8 @@ mod tests {
"got: {}",
rows[0].value
);
// No config yet → the onboarding step must offer the setup prompt.
assert!(!report.configured());
}

#[test]
Expand All @@ -722,6 +750,9 @@ mod tests {
assert_eq!(rows[0].tone, McpRowTone::Bad);
assert_eq!(rows[0].label, "Config error:");
assert!(!rows[0].value.is_empty());
// File exists (user configured something) → show the error, not the
// onboarding prompt.
assert!(report.configured());
}

#[test]
Expand All @@ -733,6 +764,8 @@ mod tests {
let rows = report.summary_rows();
assert_eq!(rows[0].tone, McpRowTone::Warn);
assert!(rows[0].value.contains("no servers"));
// Present but empty config counts as "not configured yet" → prompt.
assert!(!report.configured());
}

#[test]
Expand Down Expand Up @@ -762,6 +795,8 @@ mod tests {
"got: {}",
row.value
);
// A config with at least one server → configured; no setup prompt.
assert!(report.configured());
}

#[tokio::test]
Expand Down
6 changes: 6 additions & 0 deletions bridge/src/agent_status.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,18 +59,24 @@ impl From<&McpStatusRow> for CsMcpStatusRow {
#[derive(uniffi::Record)]
pub struct CsMcpStatusReport {
pub config_path_display: String,
/// `false` when there is no MCP config yet (missing `mcp.json` or a present
/// config with no servers). The onboarding readiness step uses this to choose
/// between the status card and the "set up MCP servers" prompt.
pub configured: bool,
pub rows: Vec<CsMcpStatusRow>,
}

impl From<McpStatusReport> for CsMcpStatusReport {
fn from(report: McpStatusReport) -> Self {
let configured = report.configured();
let rows = report
.summary_rows()
.iter()
.map(CsMcpStatusRow::from)
.collect();
Self {
config_path_display: report.config_path_display,
configured,
rows,
}
}
Expand Down
2 changes: 1 addition & 1 deletion macos/Codescribe/Screens/Onboarding/OnboardingSteps.swift
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ struct AgenticReadinessStepView: View {
statusCard(rows: readiness.rows)
}

if let mcp = model.mcpStatus, !mcp.rows.isEmpty {
if let mcp = model.mcpStatus, mcp.configured {
Text("MCP servers")
.font(CSFont.mono(10, .semibold))
.tracking(0.4)
Expand Down
1 change: 1 addition & 0 deletions macos/Codescribe/Screens/Settings/AgentStatusEngine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ extension CsMcpStatusReport {
/// Sample MCP status with a mix of live / pending servers (preview seed).
static let sample = CsMcpStatusReport(
configPathDisplay: "~/.codescribe/mcp.json",
configured: true,
rows: [
CsMcpStatusRow(label: "loctree-mcp:", value: "9 tool(s)", tone: .good),
CsMcpStatusRow(label: "aicx-mcp:", value: "configured (agent not started)", tone: .warn),
Expand Down
Loading