Redesign Extensions page. Remove enable toggle in UI. Treat Extension Manager as core MCP enabler per session. (#8940)

Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com>
This commit is contained in:
morgmart
2026-05-01 19:16:55 -07:00
committed by GitHub
parent c365e7b950
commit 45d8bf81d0
67 changed files with 2415 additions and 980 deletions
+4 -2
View File
@@ -1986,10 +1986,12 @@ extensions:
)
.unwrap();
// User config (higher priority / write target) disables developer and adds a new extension
// User config (higher priority / write target) has already migrated, then disables
// developer and adds a new extension.
std::fs::write(
local_file.path(),
r#"
extensions_on_demand_migration: true
extensions:
developer:
enabled: false
@@ -2014,7 +2016,7 @@ extensions:
let values = config.load()?;
let extensions = values.get("extensions").unwrap().as_mapping().unwrap();
// developer should be disabled (user config overrides system)
// developer should be disabled (user config overrides system after migration)
let dev = extensions.get("developer").unwrap().as_mapping().unwrap();
assert!(!dev.get("enabled").unwrap().as_bool().unwrap());
// Fields from the system config should be preserved via merge
+23
View File
@@ -13,8 +13,13 @@ pub const DEFAULT_EXTENSION_DESCRIPTION: &str = "";
pub const DEFAULT_DISPLAY_NAME: &str = "Developer";
const EXTENSIONS_CONFIG_KEY: &str = "extensions";
fn default_extension_enabled() -> bool {
true
}
#[derive(Debug, Deserialize, Serialize, Clone, ToSchema)]
pub struct ExtensionEntry {
#[serde(default = "default_extension_enabled")]
pub enabled: bool,
#[serde(flatten)]
pub config: ExtensionConfig,
@@ -210,4 +215,22 @@ mod tests {
assert!(!is_extension_available(&unknown_platform));
assert!(is_extension_available(&builtin));
}
#[test]
fn test_extension_entry_defaults_missing_enabled_to_true() {
let value = serde_yaml::from_str(
r#"
type: stdio
name: github
description: GitHub tools
cmd: npx
args: []
"#,
)
.unwrap();
let entry: ExtensionEntry = serde_yaml::from_value(value).unwrap();
assert!(entry.enabled);
}
}
+108 -1
View File
@@ -114,10 +114,18 @@ mod tests {
assert!(changed);
let extensions_key = serde_yaml::Value::String(EXTENSIONS_CONFIG_KEY.to_string());
assert!(config.contains_key(&extensions_key));
let extensions = config.get(&extensions_key).unwrap().as_mapping().unwrap();
for (key, value) in extensions {
let key = key.as_str().unwrap();
let def = PLATFORM_EXTENSIONS.get(key).unwrap();
let entry: ExtensionEntry = serde_yaml::from_value(value.clone()).unwrap();
assert_eq!(entry.enabled, def.default_enabled);
}
}
#[test]
fn test_migrate_platform_extensions_preserves_enabled_state() {
fn test_migrate_platform_extensions_refreshes_metadata_without_changing_enabled() {
let mut config = Mapping::new();
let mut extensions = Mapping::new();
let todo_entry = ExtensionEntry {
@@ -149,6 +157,105 @@ mod tests {
let todo_entry: ExtensionEntry = serde_yaml::from_value(todo_value.clone()).unwrap();
assert!(!todo_entry.enabled);
match todo_entry.config {
ExtensionConfig::Platform {
description,
display_name,
..
} => {
assert_ne!(description, "old description");
assert_ne!(display_name.as_deref(), Some("Old Name"));
}
other => panic!("expected platform extension, got {other:?}"),
}
}
#[test]
fn test_migrate_platform_extensions_preserves_existing_enabled_values() {
let mut config = Mapping::new();
let mut extensions = Mapping::new();
let analyze_entry = ExtensionEntry {
config: ExtensionConfig::Platform {
name: "analyze".to_string(),
description: "Analyze code structure".to_string(),
display_name: Some("Analyze".to_string()),
bundled: Some(true),
available_tools: Vec::new(),
},
enabled: true,
};
let developer_entry = ExtensionEntry {
config: ExtensionConfig::Platform {
name: "developer".to_string(),
description: "Write and edit files, and execute shell commands".to_string(),
display_name: Some("Developer".to_string()),
bundled: Some(true),
available_tools: Vec::new(),
},
enabled: false,
};
let custom_developer_entry = ExtensionEntry {
config: ExtensionConfig::Stdio {
name: "developer".to_string(),
description: "Custom user developer tools".to_string(),
cmd: "custom-developer".to_string(),
args: Vec::new(),
envs: Default::default(),
env_keys: Vec::new(),
timeout: Some(300),
bundled: None,
available_tools: Vec::new(),
},
enabled: true,
};
extensions.insert(
serde_yaml::Value::String("analyze".to_string()),
serde_yaml::to_value(&analyze_entry).unwrap(),
);
extensions.insert(
serde_yaml::Value::String("developer".to_string()),
serde_yaml::to_value(&developer_entry).unwrap(),
);
extensions.insert(
serde_yaml::Value::String("custom-developer".to_string()),
serde_yaml::to_value(&custom_developer_entry).unwrap(),
);
config.insert(
serde_yaml::Value::String(EXTENSIONS_CONFIG_KEY.to_string()),
serde_yaml::Value::Mapping(extensions),
);
assert!(run_migrations(&mut config));
let extensions_key = serde_yaml::Value::String(EXTENSIONS_CONFIG_KEY.to_string());
let extensions = config.get(&extensions_key).unwrap().as_mapping().unwrap();
let analyze: ExtensionEntry = serde_yaml::from_value(
extensions
.get(serde_yaml::Value::String("analyze".to_string()))
.unwrap()
.clone(),
)
.unwrap();
let developer: ExtensionEntry = serde_yaml::from_value(
extensions
.get(serde_yaml::Value::String("developer".to_string()))
.unwrap()
.clone(),
)
.unwrap();
let custom_developer: ExtensionEntry = serde_yaml::from_value(
extensions
.get(serde_yaml::Value::String("custom-developer".to_string()))
.unwrap()
.clone(),
)
.unwrap();
assert!(analyze.enabled);
assert!(!developer.enabled);
assert!(custom_developer.enabled);
}
#[test]