mirror of
https://github.com/instructkr/claw-code.git
synced 2026-05-19 14:01:26 +08:00
omx(team): auto-checkpoint worker-1 [1]
This commit is contained in:
@@ -4188,6 +4188,7 @@ fn mcp_server_details_json(config: &McpServerConfig) -> Value {
|
|||||||
fn mcp_server_json(name: &str, server: &ScopedMcpServerConfig) -> Value {
|
fn mcp_server_json(name: &str, server: &ScopedMcpServerConfig) -> Value {
|
||||||
json!({
|
json!({
|
||||||
"name": name,
|
"name": name,
|
||||||
|
"required": server.required,
|
||||||
"scope": config_source_json(server.scope),
|
"scope": config_source_json(server.scope),
|
||||||
"transport": mcp_transport_json(&server.config),
|
"transport": mcp_transport_json(&server.config),
|
||||||
"summary": mcp_server_summary(&server.config),
|
"summary": mcp_server_summary(&server.config),
|
||||||
|
|||||||
@@ -101,6 +101,7 @@ pub struct McpConfigCollection {
|
|||||||
/// MCP server config paired with the scope that defined it.
|
/// MCP server config paired with the scope that defined it.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct ScopedMcpServerConfig {
|
pub struct ScopedMcpServerConfig {
|
||||||
|
pub required: bool,
|
||||||
pub scope: ConfigSource,
|
pub scope: ConfigSource,
|
||||||
pub config: McpServerConfig,
|
pub config: McpServerConfig,
|
||||||
}
|
}
|
||||||
@@ -752,6 +753,12 @@ fn merge_mcp_servers(
|
|||||||
target.insert(
|
target.insert(
|
||||||
name.clone(),
|
name.clone(),
|
||||||
ScopedMcpServerConfig {
|
ScopedMcpServerConfig {
|
||||||
|
required: optional_bool(
|
||||||
|
expect_object(value, &format!("{}: mcpServers.{name}", path.display()))?,
|
||||||
|
"required",
|
||||||
|
&format!("{}: mcpServers.{name}", path.display()),
|
||||||
|
)?
|
||||||
|
.unwrap_or(false),
|
||||||
scope: source,
|
scope: source,
|
||||||
config: parsed,
|
config: parsed,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -275,10 +275,12 @@ mod tests {
|
|||||||
oauth: None,
|
oauth: None,
|
||||||
});
|
});
|
||||||
let user = ScopedMcpServerConfig {
|
let user = ScopedMcpServerConfig {
|
||||||
|
required: false,
|
||||||
scope: ConfigSource::User,
|
scope: ConfigSource::User,
|
||||||
config: base_config.clone(),
|
config: base_config.clone(),
|
||||||
};
|
};
|
||||||
let local = ScopedMcpServerConfig {
|
let local = ScopedMcpServerConfig {
|
||||||
|
required: false,
|
||||||
scope: ConfigSource::Local,
|
scope: ConfigSource::Local,
|
||||||
config: base_config,
|
config: base_config,
|
||||||
};
|
};
|
||||||
@@ -288,6 +290,7 @@ mod tests {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let changed = ScopedMcpServerConfig {
|
let changed = ScopedMcpServerConfig {
|
||||||
|
required: false,
|
||||||
scope: ConfigSource::Local,
|
scope: ConfigSource::Local,
|
||||||
config: McpServerConfig::Http(McpRemoteServerConfig {
|
config: McpServerConfig::Http(McpRemoteServerConfig {
|
||||||
url: "https://vendor.example/v2/mcp".to_string(),
|
url: "https://vendor.example/v2/mcp".to_string(),
|
||||||
|
|||||||
@@ -143,6 +143,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn bootstraps_stdio_servers_into_transport_targets() {
|
fn bootstraps_stdio_servers_into_transport_targets() {
|
||||||
let config = ScopedMcpServerConfig {
|
let config = ScopedMcpServerConfig {
|
||||||
|
required: false,
|
||||||
scope: ConfigSource::User,
|
scope: ConfigSource::User,
|
||||||
config: McpServerConfig::Stdio(McpStdioServerConfig {
|
config: McpServerConfig::Stdio(McpStdioServerConfig {
|
||||||
command: "uvx".to_string(),
|
command: "uvx".to_string(),
|
||||||
@@ -176,6 +177,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn bootstraps_remote_servers_with_oauth_auth() {
|
fn bootstraps_remote_servers_with_oauth_auth() {
|
||||||
let config = ScopedMcpServerConfig {
|
let config = ScopedMcpServerConfig {
|
||||||
|
required: false,
|
||||||
scope: ConfigSource::Project,
|
scope: ConfigSource::Project,
|
||||||
config: McpServerConfig::Http(McpRemoteServerConfig {
|
config: McpServerConfig::Http(McpRemoteServerConfig {
|
||||||
url: "https://vendor.example/mcp".to_string(),
|
url: "https://vendor.example/mcp".to_string(),
|
||||||
@@ -213,6 +215,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn bootstraps_websocket_and_sdk_transports_without_oauth() {
|
fn bootstraps_websocket_and_sdk_transports_without_oauth() {
|
||||||
let ws = ScopedMcpServerConfig {
|
let ws = ScopedMcpServerConfig {
|
||||||
|
required: false,
|
||||||
scope: ConfigSource::Local,
|
scope: ConfigSource::Local,
|
||||||
config: McpServerConfig::Ws(McpWebSocketServerConfig {
|
config: McpServerConfig::Ws(McpWebSocketServerConfig {
|
||||||
url: "wss://vendor.example/mcp".to_string(),
|
url: "wss://vendor.example/mcp".to_string(),
|
||||||
@@ -221,6 +224,7 @@ mod tests {
|
|||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
let sdk = ScopedMcpServerConfig {
|
let sdk = ScopedMcpServerConfig {
|
||||||
|
required: false,
|
||||||
scope: ConfigSource::Local,
|
scope: ConfigSource::Local,
|
||||||
config: McpServerConfig::Sdk(McpSdkServerConfig {
|
config: McpServerConfig::Sdk(McpSdkServerConfig {
|
||||||
name: "sdk-server".to_string(),
|
name: "sdk-server".to_string(),
|
||||||
|
|||||||
@@ -230,6 +230,7 @@ pub struct ManagedMcpTool {
|
|||||||
pub struct UnsupportedMcpServer {
|
pub struct UnsupportedMcpServer {
|
||||||
pub server_name: String,
|
pub server_name: String,
|
||||||
pub transport: McpTransport,
|
pub transport: McpTransport,
|
||||||
|
pub required: bool,
|
||||||
pub reason: String,
|
pub reason: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,6 +238,7 @@ pub struct UnsupportedMcpServer {
|
|||||||
pub struct McpDiscoveryFailure {
|
pub struct McpDiscoveryFailure {
|
||||||
pub server_name: String,
|
pub server_name: String,
|
||||||
pub phase: McpLifecyclePhase,
|
pub phase: McpLifecyclePhase,
|
||||||
|
pub required: bool,
|
||||||
pub error: String,
|
pub error: String,
|
||||||
pub recoverable: bool,
|
pub recoverable: bool,
|
||||||
pub context: BTreeMap<String, String>,
|
pub context: BTreeMap<String, String>,
|
||||||
@@ -366,7 +368,7 @@ impl McpServerManagerError {
|
|||||||
) && matches!(self, Self::Transport { .. } | Self::Timeout { .. })
|
) && 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 phase = self.lifecycle_phase();
|
||||||
let recoverable = self.recoverable();
|
let recoverable = self.recoverable();
|
||||||
let context = self.error_context();
|
let context = self.error_context();
|
||||||
@@ -374,6 +376,7 @@ impl McpServerManagerError {
|
|||||||
McpDiscoveryFailure {
|
McpDiscoveryFailure {
|
||||||
server_name: server_name.to_string(),
|
server_name: server_name.to_string(),
|
||||||
phase,
|
phase,
|
||||||
|
required,
|
||||||
error: self.to_string(),
|
error: self.to_string(),
|
||||||
recoverable,
|
recoverable,
|
||||||
context,
|
context,
|
||||||
@@ -447,7 +450,10 @@ fn unsupported_server_failed_server(server: &UnsupportedMcpServer) -> McpFailedS
|
|||||||
McpLifecyclePhase::ServerRegistration,
|
McpLifecyclePhase::ServerRegistration,
|
||||||
Some(server.server_name.clone()),
|
Some(server.server_name.clone()),
|
||||||
server.reason.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,
|
false,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
@@ -464,14 +470,16 @@ struct ManagedMcpServer {
|
|||||||
bootstrap: McpClientBootstrap,
|
bootstrap: McpClientBootstrap,
|
||||||
process: Option<McpStdioProcess>,
|
process: Option<McpStdioProcess>,
|
||||||
initialized: bool,
|
initialized: bool,
|
||||||
|
required: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ManagedMcpServer {
|
impl ManagedMcpServer {
|
||||||
fn new(bootstrap: McpClientBootstrap) -> Self {
|
fn new(bootstrap: McpClientBootstrap, required: bool) -> Self {
|
||||||
Self {
|
Self {
|
||||||
bootstrap,
|
bootstrap,
|
||||||
process: None,
|
process: None,
|
||||||
initialized: false,
|
initialized: false,
|
||||||
|
required,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -498,11 +506,15 @@ impl McpServerManager {
|
|||||||
for (server_name, server_config) in servers {
|
for (server_name, server_config) in servers {
|
||||||
if server_config.transport() == McpTransport::Stdio {
|
if server_config.transport() == McpTransport::Stdio {
|
||||||
let bootstrap = McpClientBootstrap::from_scoped_config(server_name, server_config);
|
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 {
|
} else {
|
||||||
unsupported_servers.push(UnsupportedMcpServer {
|
unsupported_servers.push(UnsupportedMcpServer {
|
||||||
server_name: server_name.clone(),
|
server_name: server_name.clone(),
|
||||||
transport: server_config.transport(),
|
transport: server_config.transport(),
|
||||||
|
required: server_config.required,
|
||||||
reason: format!(
|
reason: format!(
|
||||||
"transport {:?} is not supported by McpServerManager",
|
"transport {:?} is not supported by McpServerManager",
|
||||||
server_config.transport()
|
server_config.transport()
|
||||||
@@ -576,7 +588,11 @@ impl McpServerManager {
|
|||||||
}
|
}
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
self.clear_routes_for_server(&server_name);
|
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,
|
failure.phase,
|
||||||
Some(failure.server_name.clone()),
|
Some(failure.server_name.clone()),
|
||||||
failure.error.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,
|
failure.recoverable,
|
||||||
),
|
),
|
||||||
})
|
})
|
||||||
@@ -1765,6 +1785,7 @@ mod tests {
|
|||||||
|
|
||||||
fn sample_bootstrap(script_path: &Path) -> McpClientBootstrap {
|
fn sample_bootstrap(script_path: &Path) -> McpClientBootstrap {
|
||||||
let config = ScopedMcpServerConfig {
|
let config = ScopedMcpServerConfig {
|
||||||
|
required: false,
|
||||||
scope: ConfigSource::Local,
|
scope: ConfigSource::Local,
|
||||||
config: McpServerConfig::Stdio(McpStdioServerConfig {
|
config: McpServerConfig::Stdio(McpStdioServerConfig {
|
||||||
command: "/bin/sh".to_string(),
|
command: "/bin/sh".to_string(),
|
||||||
@@ -1832,6 +1853,7 @@ mod tests {
|
|||||||
]);
|
]);
|
||||||
env.extend(extra_env);
|
env.extend(extra_env);
|
||||||
ScopedMcpServerConfig {
|
ScopedMcpServerConfig {
|
||||||
|
required: false,
|
||||||
scope: ConfigSource::Local,
|
scope: ConfigSource::Local,
|
||||||
config: McpServerConfig::Stdio(McpStdioServerConfig {
|
config: McpServerConfig::Stdio(McpStdioServerConfig {
|
||||||
command: "python3".to_string(),
|
command: "python3".to_string(),
|
||||||
@@ -1874,6 +1896,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn rejects_non_stdio_bootstrap() {
|
fn rejects_non_stdio_bootstrap() {
|
||||||
let config = ScopedMcpServerConfig {
|
let config = ScopedMcpServerConfig {
|
||||||
|
required: false,
|
||||||
scope: ConfigSource::Local,
|
scope: ConfigSource::Local,
|
||||||
config: McpServerConfig::Sdk(crate::config::McpSdkServerConfig {
|
config: McpServerConfig::Sdk(crate::config::McpSdkServerConfig {
|
||||||
name: "sdk-server".to_string(),
|
name: "sdk-server".to_string(),
|
||||||
@@ -2310,6 +2333,7 @@ mod tests {
|
|||||||
let servers = BTreeMap::from([(
|
let servers = BTreeMap::from([(
|
||||||
"slow".to_string(),
|
"slow".to_string(),
|
||||||
ScopedMcpServerConfig {
|
ScopedMcpServerConfig {
|
||||||
|
required: false,
|
||||||
scope: ConfigSource::Local,
|
scope: ConfigSource::Local,
|
||||||
config: McpServerConfig::Stdio(McpStdioServerConfig {
|
config: McpServerConfig::Stdio(McpStdioServerConfig {
|
||||||
command: "python3".to_string(),
|
command: "python3".to_string(),
|
||||||
@@ -2363,6 +2387,7 @@ mod tests {
|
|||||||
let servers = BTreeMap::from([(
|
let servers = BTreeMap::from([(
|
||||||
"broken".to_string(),
|
"broken".to_string(),
|
||||||
ScopedMcpServerConfig {
|
ScopedMcpServerConfig {
|
||||||
|
required: false,
|
||||||
scope: ConfigSource::Local,
|
scope: ConfigSource::Local,
|
||||||
config: McpServerConfig::Stdio(McpStdioServerConfig {
|
config: McpServerConfig::Stdio(McpStdioServerConfig {
|
||||||
command: "python3".to_string(),
|
command: "python3".to_string(),
|
||||||
@@ -2777,6 +2802,7 @@ mod tests {
|
|||||||
(
|
(
|
||||||
"http".to_string(),
|
"http".to_string(),
|
||||||
ScopedMcpServerConfig {
|
ScopedMcpServerConfig {
|
||||||
|
required: false,
|
||||||
scope: ConfigSource::Local,
|
scope: ConfigSource::Local,
|
||||||
config: McpServerConfig::Http(McpRemoteServerConfig {
|
config: McpServerConfig::Http(McpRemoteServerConfig {
|
||||||
url: "https://example.test/mcp".to_string(),
|
url: "https://example.test/mcp".to_string(),
|
||||||
@@ -2789,6 +2815,7 @@ mod tests {
|
|||||||
(
|
(
|
||||||
"sdk".to_string(),
|
"sdk".to_string(),
|
||||||
ScopedMcpServerConfig {
|
ScopedMcpServerConfig {
|
||||||
|
required: false,
|
||||||
scope: ConfigSource::Local,
|
scope: ConfigSource::Local,
|
||||||
config: McpServerConfig::Sdk(McpSdkServerConfig {
|
config: McpServerConfig::Sdk(McpSdkServerConfig {
|
||||||
name: "sdk-server".to_string(),
|
name: "sdk-server".to_string(),
|
||||||
@@ -2798,6 +2825,7 @@ mod tests {
|
|||||||
(
|
(
|
||||||
"ws".to_string(),
|
"ws".to_string(),
|
||||||
ScopedMcpServerConfig {
|
ScopedMcpServerConfig {
|
||||||
|
required: false,
|
||||||
scope: ConfigSource::Local,
|
scope: ConfigSource::Local,
|
||||||
config: McpServerConfig::Ws(McpWebSocketServerConfig {
|
config: McpServerConfig::Ws(McpWebSocketServerConfig {
|
||||||
url: "wss://example.test/mcp".to_string(),
|
url: "wss://example.test/mcp".to_string(),
|
||||||
|
|||||||
@@ -442,6 +442,7 @@ mod tests {
|
|||||||
log_path: &Path,
|
log_path: &Path,
|
||||||
) -> ScopedMcpServerConfig {
|
) -> ScopedMcpServerConfig {
|
||||||
ScopedMcpServerConfig {
|
ScopedMcpServerConfig {
|
||||||
|
required: false,
|
||||||
scope: ConfigSource::Local,
|
scope: ConfigSource::Local,
|
||||||
config: McpServerConfig::Stdio(McpStdioServerConfig {
|
config: McpServerConfig::Stdio(McpStdioServerConfig {
|
||||||
command: "python3".to_string(),
|
command: "python3".to_string(),
|
||||||
|
|||||||
@@ -4542,7 +4542,10 @@ impl RuntimeMcpState {
|
|||||||
runtime::McpLifecyclePhase::ToolDiscovery,
|
runtime::McpLifecyclePhase::ToolDiscovery,
|
||||||
Some(failure.server_name.clone()),
|
Some(failure.server_name.clone()),
|
||||||
failure.error.clone(),
|
failure.error.clone(),
|
||||||
std::collections::BTreeMap::new(),
|
std::collections::BTreeMap::from([(
|
||||||
|
"required".to_string(),
|
||||||
|
failure.required.to_string(),
|
||||||
|
)]),
|
||||||
true,
|
true,
|
||||||
),
|
),
|
||||||
})
|
})
|
||||||
@@ -4557,6 +4560,9 @@ impl RuntimeMcpState {
|
|||||||
std::collections::BTreeMap::from([(
|
std::collections::BTreeMap::from([(
|
||||||
"transport".to_string(),
|
"transport".to_string(),
|
||||||
format!("{:?}", server.transport).to_ascii_lowercase(),
|
format!("{:?}", server.transport).to_ascii_lowercase(),
|
||||||
|
), (
|
||||||
|
"required".to_string(),
|
||||||
|
server.required.to_string(),
|
||||||
)]),
|
)]),
|
||||||
false,
|
false,
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user