use crate::core::config::Config;
use crate::core::mcp_catalog::config::GatewayServer;
use super::addon_manifest::AddonManifest;
#[derive(Debug, PartialEq, Eq)]
pub enum Wired {
NothingToWire,
Added(String),
Replaced(String),
}
pub fn register(manifest: &AddonManifest) -> Result<Wired, String> {
let Some(wiring) = manifest.mcp.as_ref() else {
return Ok(Wired::NothingToWire);
};
let name = manifest.addon.name.clone();
let mut server: GatewayServer = wiring.to_gateway_server(&name);
let mut cfg = Config::load();
let previous = cfg.gateway.servers.iter().find(|s| s.name == name).cloned();
if let Some(prev) = &previous {
server.secret_env = prev.secret_env.clone();
server.secret_headers = prev.secret_headers.clone();
server.enabled = prev.enabled;
server.oauth |= prev.oauth;
if server.oauth_scopes.is_empty() {
server.oauth_scopes.clone_from(&prev.oauth_scopes);
}
}
cfg.gateway.servers.retain(|s| s.name != name);
cfg.gateway.servers.push(server);
cfg.save().map_err(|e| format!("save config: {e}"))?;
Ok(if previous.is_some() {
Wired::Replaced(name)
} else {
Wired::Added(name)
})
}
pub fn unregister(name: &str) -> Result<bool, String> {
let mut cfg = Config::load();
let Some(removed) = cfg.gateway.servers.iter().find(|s| s.name == name).cloned() else {
return Ok(false);
};
cfg.gateway.servers.retain(|s| s.name != name);
cfg.save().map_err(|e| format!("save config: {e}"))?;
#[cfg(feature = "http-server")]
if removed.oauth {
crate::core::mcp_catalog::oauth::forget(&removed.url)?;
}
#[cfg(not(feature = "http-server"))]
let _ = removed;
Ok(true)
}
pub fn gateway_enabled() -> bool {
Config::load().gateway.enabled
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::context_package::addon_manifest;
const STDIO: &str = r#"
[addon]
name = "mdcast"
[mcp]
command = "mdcast"
args = ["mcp", "serve"]
"#;
#[test]
fn a_wasm_only_addon_wires_nothing() {
let m = addon_manifest::parse("[addon]\nname = \"squeeze\"\n").unwrap();
assert_eq!(register(&m).unwrap(), Wired::NothingToWire);
}
#[test]
fn registering_adds_then_replaces_rather_than_duplicating() {
let _iso = crate::core::data_dir::isolated_data_dir();
let m = addon_manifest::parse(STDIO).unwrap();
assert_eq!(register(&m).unwrap(), Wired::Added("mdcast".into()));
let cfg = Config::load();
let entry = cfg
.gateway
.servers
.iter()
.find(|s| s.name == "mdcast")
.expect("wired");
assert_eq!(entry.command, "mdcast");
assert_eq!(entry.args, vec!["mcp", "serve"]);
assert_eq!(register(&m).unwrap(), Wired::Replaced("mdcast".into()));
assert_eq!(
Config::load()
.gateway
.servers
.iter()
.filter(|s| s.name == "mdcast")
.count(),
1
);
}
#[test]
fn an_upgrade_keeps_the_users_secrets_and_their_off_switch() {
let _iso = crate::core::data_dir::isolated_data_dir();
let m = addon_manifest::parse(STDIO).unwrap();
register(&m).unwrap();
let mut cfg = Config::load();
{
let entry = cfg
.gateway
.servers
.iter_mut()
.find(|s| s.name == "mdcast")
.expect("wired");
entry.secret_env.insert(
"API_TOKEN".to_string(),
crate::core::mcp_catalog::config::SecretMementoRef {
id: "memento-1".to_string(),
format: String::new(),
},
);
entry.enabled = false;
}
cfg.save().unwrap();
let upgraded = addon_manifest::parse(
"[addon]\nname = \"mdcast\"\n[mcp]\ncommand = \"mdcast\"\nargs = [\"mcp\", \"v2\"]\n",
)
.unwrap();
assert_eq!(
register(&upgraded).unwrap(),
Wired::Replaced("mdcast".into())
);
let entry = Config::load()
.gateway
.servers
.into_iter()
.find(|s| s.name == "mdcast")
.expect("still wired");
assert_eq!(entry.args, vec!["mcp", "v2"], "the author's change applies");
assert!(
entry.secret_env.contains_key("API_TOKEN"),
"the user's credential survived the upgrade"
);
assert!(
!entry.enabled,
"a deliberate off switch is not flipped back on by an upgrade"
);
}
#[test]
fn unregister_removes_the_entry_and_reports_whether_it_was_there() {
let _iso = crate::core::data_dir::isolated_data_dir();
let m = addon_manifest::parse(STDIO).unwrap();
register(&m).unwrap();
assert!(unregister("mdcast").unwrap(), "it was wired");
assert!(
!Config::load()
.gateway
.servers
.iter()
.any(|s| s.name == "mdcast"),
"a removed addon must not keep spawning a process"
);
assert!(!unregister("mdcast").unwrap(), "second call is a no-op");
}
#[test]
fn registering_does_not_enable_the_gateway() {
let _iso = crate::core::data_dir::isolated_data_dir();
let before = Config::load().gateway.enabled;
register(&addon_manifest::parse(STDIO).unwrap()).unwrap();
assert_eq!(
Config::load().gateway.enabled,
before,
"an addon may not turn the gateway on for the user"
);
}
}