From 686cc89a36d0c795fd1ff99dafcf0c2f2d0517f7 Mon Sep 17 00:00:00 2001 From: bellman Date: Fri, 15 May 2026 10:00:39 +0900 Subject: [PATCH] omx(team): auto-checkpoint worker-1 [1] --- rust/crates/commands/src/lib.rs | 1 + rust/crates/runtime/src/config.rs | 7 ++++ rust/crates/runtime/src/mcp.rs | 3 ++ rust/crates/runtime/src/mcp_client.rs | 4 +++ rust/crates/runtime/src/mcp_stdio.rs | 40 ++++++++++++++++++---- rust/crates/runtime/src/mcp_tool_bridge.rs | 1 + rust/crates/rusty-claude-cli/src/main.rs | 8 ++++- 7 files changed, 57 insertions(+), 7 deletions(-) diff --git a/rust/crates/commands/src/lib.rs b/rust/crates/commands/src/lib.rs index c7f7085b..4a7f86ab 100644 --- a/rust/crates/commands/src/lib.rs +++ b/rust/crates/commands/src/lib.rs @@ -4188,6 +4188,7 @@ fn mcp_server_details_json(config: &McpServerConfig) -> Value { fn mcp_server_json(name: &str, server: &ScopedMcpServerConfig) -> Value { json!({ "name": name, + "required": server.required, "scope": config_source_json(server.scope), "transport": mcp_transport_json(&server.config), "summary": mcp_server_summary(&server.config), diff --git a/rust/crates/runtime/src/config.rs b/rust/crates/runtime/src/config.rs index a7c6fa84..a38cc562 100644 --- a/rust/crates/runtime/src/config.rs +++ b/rust/crates/runtime/src/config.rs @@ -101,6 +101,7 @@ pub struct McpConfigCollection { /// MCP server config paired with the scope that defined it. #[derive(Debug, Clone, PartialEq, Eq)] pub struct ScopedMcpServerConfig { + pub required: bool, pub scope: ConfigSource, pub config: McpServerConfig, } @@ -752,6 +753,12 @@ fn merge_mcp_servers( target.insert( name.clone(), ScopedMcpServerConfig { + required: optional_bool( + expect_object(value, &format!("{}: mcpServers.{name}", path.display()))?, + "required", + &format!("{}: mcpServers.{name}", path.display()), + )? + .unwrap_or(false), scope: source, config: parsed, }, diff --git a/rust/crates/runtime/src/mcp.rs b/rust/crates/runtime/src/mcp.rs index e65cd084..6e2a13b8 100644 --- a/rust/crates/runtime/src/mcp.rs +++ b/rust/crates/runtime/src/mcp.rs @@ -275,10 +275,12 @@ mod tests { oauth: None, }); let user = ScopedMcpServerConfig { + required: false, scope: ConfigSource::User, config: base_config.clone(), }; let local = ScopedMcpServerConfig { + required: false, scope: ConfigSource::Local, config: base_config, }; @@ -288,6 +290,7 @@ mod tests { ); let changed = ScopedMcpServerConfig { + required: false, scope: ConfigSource::Local, config: McpServerConfig::Http(McpRemoteServerConfig { url: "https://vendor.example/v2/mcp".to_string(), diff --git a/rust/crates/runtime/src/mcp_client.rs b/rust/crates/runtime/src/mcp_client.rs index 96a6db2f..c017e494 100644 --- a/rust/crates/runtime/src/mcp_client.rs +++ b/rust/crates/runtime/src/mcp_client.rs @@ -143,6 +143,7 @@ mod tests { #[test] fn bootstraps_stdio_servers_into_transport_targets() { let config = ScopedMcpServerConfig { + required: false, scope: ConfigSource::User, config: McpServerConfig::Stdio(McpStdioServerConfig { command: "uvx".to_string(), @@ -176,6 +177,7 @@ mod tests { #[test] fn bootstraps_remote_servers_with_oauth_auth() { let config = ScopedMcpServerConfig { + required: false, scope: ConfigSource::Project, config: McpServerConfig::Http(McpRemoteServerConfig { url: "https://vendor.example/mcp".to_string(), @@ -213,6 +215,7 @@ mod tests { #[test] fn bootstraps_websocket_and_sdk_transports_without_oauth() { let ws = ScopedMcpServerConfig { + required: false, scope: ConfigSource::Local, config: McpServerConfig::Ws(McpWebSocketServerConfig { url: "wss://vendor.example/mcp".to_string(), @@ -221,6 +224,7 @@ mod tests { }), }; let sdk = ScopedMcpServerConfig { + required: false, scope: ConfigSource::Local, config: McpServerConfig::Sdk(McpSdkServerConfig { name: "sdk-server".to_string(), diff --git a/rust/crates/runtime/src/mcp_stdio.rs b/rust/crates/runtime/src/mcp_stdio.rs index 5fbc31ba..72ec1536 100644 --- a/rust/crates/runtime/src/mcp_stdio.rs +++ b/rust/crates/runtime/src/mcp_stdio.rs @@ -230,6 +230,7 @@ pub struct ManagedMcpTool { pub struct UnsupportedMcpServer { pub server_name: String, pub transport: McpTransport, + pub required: bool, pub reason: String, } @@ -237,6 +238,7 @@ pub struct UnsupportedMcpServer { pub struct McpDiscoveryFailure { pub server_name: String, pub phase: McpLifecyclePhase, + pub required: bool, pub error: String, pub recoverable: bool, pub context: BTreeMap, @@ -366,7 +368,7 @@ impl McpServerManagerError { ) && matches!(self, Self::Transport { .. } | Self::Timeout { .. }) } - fn discovery_failure(&self, server_name: &str) -> McpDiscoveryFailure { + fn discovery_failure(&self, server_name: &str, required: bool) -> McpDiscoveryFailure { let phase = self.lifecycle_phase(); let recoverable = self.recoverable(); let context = self.error_context(); @@ -374,6 +376,7 @@ impl McpServerManagerError { McpDiscoveryFailure { server_name: server_name.to_string(), phase, + required, error: self.to_string(), recoverable, context, @@ -447,7 +450,10 @@ fn unsupported_server_failed_server(server: &UnsupportedMcpServer) -> McpFailedS McpLifecyclePhase::ServerRegistration, Some(server.server_name.clone()), server.reason.clone(), - BTreeMap::from([("transport".to_string(), format!("{:?}", server.transport))]), + BTreeMap::from([ + ("transport".to_string(), format!("{:?}", server.transport)), + ("required".to_string(), server.required.to_string()), + ]), false, ), } @@ -464,14 +470,16 @@ struct ManagedMcpServer { bootstrap: McpClientBootstrap, process: Option, initialized: bool, + required: bool, } impl ManagedMcpServer { - fn new(bootstrap: McpClientBootstrap) -> Self { + fn new(bootstrap: McpClientBootstrap, required: bool) -> Self { Self { bootstrap, process: None, initialized: false, + required, } } } @@ -498,11 +506,15 @@ impl McpServerManager { for (server_name, server_config) in servers { if server_config.transport() == McpTransport::Stdio { let bootstrap = McpClientBootstrap::from_scoped_config(server_name, server_config); - managed_servers.insert(server_name.clone(), ManagedMcpServer::new(bootstrap)); + managed_servers.insert( + server_name.clone(), + ManagedMcpServer::new(bootstrap, server_config.required), + ); } else { unsupported_servers.push(UnsupportedMcpServer { server_name: server_name.clone(), transport: server_config.transport(), + required: server_config.required, reason: format!( "transport {:?} is not supported by McpServerManager", server_config.transport() @@ -576,7 +588,11 @@ impl McpServerManager { } Err(error) => { self.clear_routes_for_server(&server_name); - failed_servers.push(error.discovery_failure(&server_name)); + let required = self + .servers + .get(&server_name) + .is_some_and(|server| server.required); + failed_servers.push(error.discovery_failure(&server_name, required)); } } } @@ -590,7 +606,11 @@ impl McpServerManager { failure.phase, Some(failure.server_name.clone()), failure.error.clone(), - failure.context.clone(), + { + let mut context = failure.context.clone(); + context.insert("required".to_string(), failure.required.to_string()); + context + }, failure.recoverable, ), }) @@ -1765,6 +1785,7 @@ mod tests { fn sample_bootstrap(script_path: &Path) -> McpClientBootstrap { let config = ScopedMcpServerConfig { + required: false, scope: ConfigSource::Local, config: McpServerConfig::Stdio(McpStdioServerConfig { command: "/bin/sh".to_string(), @@ -1832,6 +1853,7 @@ mod tests { ]); env.extend(extra_env); ScopedMcpServerConfig { + required: false, scope: ConfigSource::Local, config: McpServerConfig::Stdio(McpStdioServerConfig { command: "python3".to_string(), @@ -1874,6 +1896,7 @@ mod tests { #[test] fn rejects_non_stdio_bootstrap() { let config = ScopedMcpServerConfig { + required: false, scope: ConfigSource::Local, config: McpServerConfig::Sdk(crate::config::McpSdkServerConfig { name: "sdk-server".to_string(), @@ -2310,6 +2333,7 @@ mod tests { let servers = BTreeMap::from([( "slow".to_string(), ScopedMcpServerConfig { + required: false, scope: ConfigSource::Local, config: McpServerConfig::Stdio(McpStdioServerConfig { command: "python3".to_string(), @@ -2363,6 +2387,7 @@ mod tests { let servers = BTreeMap::from([( "broken".to_string(), ScopedMcpServerConfig { + required: false, scope: ConfigSource::Local, config: McpServerConfig::Stdio(McpStdioServerConfig { command: "python3".to_string(), @@ -2777,6 +2802,7 @@ mod tests { ( "http".to_string(), ScopedMcpServerConfig { + required: false, scope: ConfigSource::Local, config: McpServerConfig::Http(McpRemoteServerConfig { url: "https://example.test/mcp".to_string(), @@ -2789,6 +2815,7 @@ mod tests { ( "sdk".to_string(), ScopedMcpServerConfig { + required: false, scope: ConfigSource::Local, config: McpServerConfig::Sdk(McpSdkServerConfig { name: "sdk-server".to_string(), @@ -2798,6 +2825,7 @@ mod tests { ( "ws".to_string(), ScopedMcpServerConfig { + required: false, scope: ConfigSource::Local, config: McpServerConfig::Ws(McpWebSocketServerConfig { url: "wss://example.test/mcp".to_string(), diff --git a/rust/crates/runtime/src/mcp_tool_bridge.rs b/rust/crates/runtime/src/mcp_tool_bridge.rs index af637a98..5b7c326c 100644 --- a/rust/crates/runtime/src/mcp_tool_bridge.rs +++ b/rust/crates/runtime/src/mcp_tool_bridge.rs @@ -442,6 +442,7 @@ mod tests { log_path: &Path, ) -> ScopedMcpServerConfig { ScopedMcpServerConfig { + required: false, scope: ConfigSource::Local, config: McpServerConfig::Stdio(McpStdioServerConfig { command: "python3".to_string(), diff --git a/rust/crates/rusty-claude-cli/src/main.rs b/rust/crates/rusty-claude-cli/src/main.rs index fa227928..b585a78c 100644 --- a/rust/crates/rusty-claude-cli/src/main.rs +++ b/rust/crates/rusty-claude-cli/src/main.rs @@ -4542,7 +4542,10 @@ impl RuntimeMcpState { runtime::McpLifecyclePhase::ToolDiscovery, Some(failure.server_name.clone()), failure.error.clone(), - std::collections::BTreeMap::new(), + std::collections::BTreeMap::from([( + "required".to_string(), + failure.required.to_string(), + )]), true, ), }) @@ -4557,6 +4560,9 @@ impl RuntimeMcpState { std::collections::BTreeMap::from([( "transport".to_string(), format!("{:?}", server.transport).to_ascii_lowercase(), + ), ( + "required".to_string(), + server.required.to_string(), )]), false, ),