diff --git a/rust/crates/commands/src/lib.rs b/rust/crates/commands/src/lib.rs index 4a7f86ab..9e9a3ced 100644 --- a/rust/crates/commands/src/lib.rs +++ b/rust/crates/commands/src/lib.rs @@ -3790,6 +3790,7 @@ fn render_mcp_server_report( format!(" Working directory {}", cwd.display()), format!(" Name {server_name}"), format!(" Scope {}", config_source_label(server.scope)), + format!(" Required {}", server.required), format!( " Transport {}", mcp_transport_label(&server.config) @@ -5584,6 +5585,7 @@ mod tests { "command": "uvx", "args": ["alpha-server"], "env": {"ALPHA_TOKEN": "secret"}, + "required": true, "toolCallTimeoutMs": 1200 }, "remote": { @@ -5629,6 +5631,7 @@ mod tests { let show = super::render_mcp_report_for(&loader, &workspace, Some("show alpha")) .expect("mcp show report should render"); assert!(show.contains("Name alpha")); + assert!(show.contains("Required true")); assert!(show.contains("Command uvx")); assert!(show.contains("Args alpha-server")); assert!(show.contains("Env keys ALPHA_TOKEN")); @@ -5661,6 +5664,7 @@ mod tests { "command": "uvx", "args": ["alpha-server"], "env": {"ALPHA_TOKEN": "secret"}, + "required": true, "toolCallTimeoutMs": 1200 }, "remote": { @@ -5697,6 +5701,7 @@ mod tests { assert_eq!(list["action"], "list"); assert_eq!(list["configured_servers"], 2); assert_eq!(list["servers"][0]["name"], "alpha"); + assert_eq!(list["servers"][0]["required"], true); assert_eq!(list["servers"][0]["transport"]["id"], "stdio"); assert_eq!(list["servers"][0]["details"]["command"], "uvx"); assert_eq!(list["servers"][1]["name"], "remote"); @@ -5712,6 +5717,7 @@ mod tests { assert_eq!(show["action"], "show"); assert_eq!(show["found"], true); assert_eq!(show["server"]["name"], "alpha"); + assert_eq!(show["server"]["required"], true); assert_eq!(show["server"]["details"]["env_keys"][0], "ALPHA_TOKEN"); assert_eq!(show["server"]["details"]["tool_call_timeout_ms"], 1200); diff --git a/rust/crates/runtime/src/config.rs b/rust/crates/runtime/src/config.rs index a38cc562..b9a9e36a 100644 --- a/rust/crates/runtime/src/config.rs +++ b/rust/crates/runtime/src/config.rs @@ -1617,7 +1617,8 @@ mod tests { "stdio-server": { "command": "uvx", "args": ["mcp-server"], - "env": {"TOKEN": "secret"} + "env": {"TOKEN": "secret"}, + "required": true }, "remote-server": { "type": "http", @@ -1666,6 +1667,7 @@ mod tests { .get("stdio-server") .expect("stdio server should exist"); assert_eq!(stdio_server.scope, ConfigSource::User); + assert!(stdio_server.required); assert_eq!(stdio_server.transport(), McpTransport::Stdio); let remote_server = loaded @@ -1673,6 +1675,7 @@ mod tests { .get("remote-server") .expect("remote server should exist"); assert_eq!(remote_server.scope, ConfigSource::Local); + assert!(!remote_server.required); assert_eq!(remote_server.transport(), McpTransport::Ws); match &remote_server.config { McpServerConfig::Ws(config) => { diff --git a/rust/crates/runtime/src/mcp.rs b/rust/crates/runtime/src/mcp.rs index 6e2a13b8..64500e8e 100644 --- a/rust/crates/runtime/src/mcp.rs +++ b/rust/crates/runtime/src/mcp.rs @@ -117,7 +117,7 @@ pub fn scoped_mcp_config_hash(config: &ScopedMcpServerConfig) -> String { format!("claudeai-proxy|{}|{}", proxy.url, proxy.id) } }; - stable_hex_hash(&rendered) + stable_hex_hash(&format!("required:{}|{rendered}", config.required)) } fn render_command_signature(command: &[String]) -> String { diff --git a/rust/crates/runtime/src/mcp_stdio.rs b/rust/crates/runtime/src/mcp_stdio.rs index e125f660..b05ea447 100644 --- a/rust/crates/runtime/src/mcp_stdio.rs +++ b/rust/crates/runtime/src/mcp_stdio.rs @@ -2726,7 +2726,7 @@ mod tests { ( "broken".to_string(), ScopedMcpServerConfig { - required: false, + required: true, scope: ConfigSource::Local, config: McpServerConfig::Stdio(McpStdioServerConfig { command: broken_script_path.display().to_string(), @@ -2748,6 +2748,7 @@ mod tests { ); assert_eq!(report.failed_servers.len(), 1); assert_eq!(report.failed_servers[0].server_name, "broken"); + assert!(report.failed_servers[0].required); assert_eq!( report.failed_servers[0].phase, McpLifecyclePhase::InitializeHandshake @@ -2768,6 +2769,14 @@ mod tests { assert_eq!(degraded.working_servers, vec!["alpha".to_string()]); assert_eq!(degraded.failed_servers.len(), 1); assert_eq!(degraded.failed_servers[0].server_name, "broken"); + assert_eq!( + degraded.failed_servers[0] + .error + .context + .get("required") + .map(String::as_str), + Some("true") + ); assert_eq!( degraded.failed_servers[0].phase, McpLifecyclePhase::InitializeHandshake @@ -2803,7 +2812,7 @@ mod tests { ( "http".to_string(), ScopedMcpServerConfig { - required: false, + required: true, scope: ConfigSource::Local, config: McpServerConfig::Http(McpRemoteServerConfig { url: "https://example.test/mcp".to_string(), @@ -2842,11 +2851,14 @@ mod tests { assert_eq!(unsupported.len(), 3); assert_eq!(unsupported[0].server_name, "http"); + assert!(unsupported[0].required); assert_eq!(unsupported[1].server_name, "sdk"); assert_eq!(unsupported[2].server_name, "ws"); + let failed = unsupported_server_failed_server(&unsupported[0]); + assert_eq!(failed.phase, McpLifecyclePhase::ServerRegistration); assert_eq!( - unsupported_server_failed_server(&unsupported[0]).phase, - McpLifecyclePhase::ServerRegistration + failed.error.context.get("required").map(String::as_str), + Some("true") ); }