use std::collections::BTreeMap;
use crate::core::mcp_catalog::config::{GatewayServer, TransportKind};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AddonMeta {
pub name: String,
pub version: String,
pub description: String,
pub homepage: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct McpWiring {
pub transport: TransportKind,
pub command: String,
pub args: Vec<String>,
pub env: BTreeMap<String, String>,
pub url: String,
pub headers: BTreeMap<String, String>,
pub sha256: String,
pub integration: String,
pub oauth: bool,
pub oauth_scopes: Vec<String>,
}
impl McpWiring {
pub fn describe(&self) -> String {
match self.transport {
TransportKind::Stdio => {
if self.args.is_empty() {
self.command.clone()
} else {
format!("{} {}", self.command, self.args.join(" "))
}
}
TransportKind::Http if self.oauth => format!("{} (OAuth browser login)", self.url),
TransportKind::Http => self.url.clone(),
}
}
pub(crate) fn to_gateway_server(&self, name: &str) -> GatewayServer {
GatewayServer {
name: name.to_string(),
transport: self.transport,
enabled: true,
command: self.command.clone(),
args: self.args.clone(),
env: self.env.clone(),
binary_sha256: self.sha256.clone(),
url: self.url.clone(),
headers: self.headers.clone(),
oauth: self.oauth,
oauth_scopes: self.oauth_scopes.clone(),
integration: self.integration.clone(),
..GatewayServer::default()
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AddonManifest {
pub addon: AddonMeta,
pub mcp: Option<McpWiring>,
}
fn string_at(table: &toml::Value, key: &str) -> String {
table
.get(key)
.and_then(toml::Value::as_str)
.unwrap_or_default()
.to_string()
}
fn string_list(table: &toml::Value, key: &str) -> Vec<String> {
table
.get(key)
.and_then(toml::Value::as_array)
.map(|a| {
a.iter()
.filter_map(|v| v.as_str().map(str::to_string))
.collect()
})
.unwrap_or_default()
}
fn string_map(table: &toml::Value, key: &str) -> BTreeMap<String, String> {
table
.get(key)
.and_then(toml::Value::as_table)
.map(|t| {
t.iter()
.filter_map(|(k, v)| v.as_str().map(|s| (k.clone(), s.to_string())))
.collect()
})
.unwrap_or_default()
}
fn parse_integration(raw: &str, table: &str) -> Result<String, String> {
let slug = raw.trim();
if slug.is_empty() {
return Ok(String::new());
}
let kind = crate::core::mcp_catalog::adapters::IntegrationKind::parse(slug);
if kind.is_none() && !slug.eq_ignore_ascii_case("none") {
return Err(format!(
"lean-ctx-addon.toml: [{table}] unknown integration `{slug}` \
(codebase-pack | code-graph | code-symbols | memory | compression | none)"
));
}
Ok(kind.as_str().to_string())
}
fn resolve_integration(
addon_table: &toml::Value,
mcp_table: &toml::Value,
) -> Result<String, String> {
let explicit = parse_integration(&string_at(mcp_table, "integration"), "mcp")?;
if !explicit.is_empty() {
return Ok(explicit);
}
let from_addon = parse_integration(&string_at(addon_table, "integration"), "addon")?;
if !from_addon.is_empty() {
return Ok(from_addon);
}
let categories = string_list(addon_table, "categories");
let derived = crate::core::mcp_catalog::adapters::IntegrationKind::from_categories(&categories);
Ok(if derived.is_none() {
String::new()
} else {
derived.as_str().to_string()
})
}
pub fn parse(text: &str) -> Result<AddonManifest, String> {
let doc: toml::Value = toml::from_str(text).map_err(|e| format!("lean-ctx-addon.toml: {e}"))?;
let addon_table = doc
.get("addon")
.ok_or("lean-ctx-addon.toml: missing [addon] table")?;
let name = string_at(addon_table, "name");
if name.trim().is_empty() {
return Err("lean-ctx-addon.toml: [addon] name is required".into());
}
let addon = AddonMeta {
name,
version: string_at(addon_table, "version"),
description: string_at(addon_table, "description"),
homepage: string_at(addon_table, "homepage"),
};
let mcp = match doc.get("mcp") {
None => None,
Some(t) => {
let transport = match string_at(t, "transport").as_str() {
"" | "stdio" => TransportKind::Stdio,
"http" => TransportKind::Http,
other => {
return Err(format!(
"lean-ctx-addon.toml: [mcp] unknown transport `{other}` (stdio | http)"
));
}
};
let command = string_at(t, "command");
let url = string_at(t, "url");
match transport {
TransportKind::Stdio if command.trim().is_empty() => {
return Err(
"lean-ctx-addon.toml: [mcp] transport=stdio requires `command`".into(),
);
}
TransportKind::Http if url.trim().is_empty() => {
return Err("lean-ctx-addon.toml: [mcp] transport=http requires `url`".into());
}
TransportKind::Http
if !(url.starts_with("http://") || url.starts_with("https://")) =>
{
return Err(format!(
"lean-ctx-addon.toml: [mcp] url must be http(s), got `{url}`"
));
}
_ => {}
}
let oauth = match string_at(t, "auth").as_str() {
"" | "none" => false,
"oauth" if transport == TransportKind::Http => true,
"oauth" => {
return Err(
"lean-ctx-addon.toml: [mcp] auth = \"oauth\" requires transport = \"http\""
.into(),
);
}
other => {
return Err(format!(
"lean-ctx-addon.toml: [mcp] unknown auth `{other}` (none | oauth)"
));
}
};
Some(McpWiring {
transport,
command,
args: string_list(t, "args"),
env: string_map(t, "env"),
url,
headers: string_map(t, "headers"),
sha256: string_at(t, "sha256"),
integration: resolve_integration(addon_table, t)?,
oauth,
oauth_scopes: string_list(t, "scopes"),
})
}
};
Ok(AddonManifest { addon, mcp })
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_a_stdio_server() {
let m = parse(
r#"
[addon]
name = "mdcast"
version = "2.0.0"
description = "Macro/directive markdown renderer"
[mcp]
transport = "stdio"
command = "mdcast"
args = ["mcp", "serve"]
env = { LEAN_MD_MODE = "strict" }
"#,
)
.expect("parse");
assert_eq!(m.addon.name, "mdcast");
let mcp = m.mcp.expect("wiring");
assert_eq!(mcp.describe(), "mdcast mcp serve");
assert_eq!(
mcp.env.get("LEAN_MD_MODE").map(String::as_str),
Some("strict")
);
let server = mcp.to_gateway_server("mdcast");
assert_eq!(server.command, "mdcast");
assert_eq!(server.args, vec!["mcp", "serve"]);
assert!(server.enabled, "a just-consented addon should work");
}
#[test]
fn parses_an_http_server() {
let m = parse(
r#"
[addon]
name = "remote"
[mcp]
transport = "http"
url = "https://example.test/mcp"
headers = { Authorization = "Bearer x" }
"#,
)
.expect("parse");
let mcp = m.mcp.expect("wiring");
assert_eq!(mcp.transport, TransportKind::Http);
assert_eq!(mcp.describe(), "https://example.test/mcp");
}
#[test]
fn a_manifest_without_mcp_is_valid() {
let m = parse("[addon]\nname = \"squeeze\"\n").expect("parse");
assert!(m.mcp.is_none());
}
#[test]
fn integration_is_read_from_the_addon_table_as_documented() {
let m = parse(
r#"
[addon]
name = "twinmind"
display_name = "TwinMind MCP"
version = "0.1.0"
description = "TwinMind MCP memories, prompts, and tasks over HTTP."
categories = ["memory"]
integration = "memory"
[mcp]
transport = "http"
url = "https://api.twinmind.com/mcp"
"#,
)
.expect("parse");
assert_eq!(m.mcp.expect("wiring").integration, "memory");
}
#[test]
fn the_mcp_table_overrides_the_addon_table() {
let m = parse(
"[addon]\nname = \"x\"\nintegration = \"memory\"\n\
[mcp]\ncommand = \"c\"\nintegration = \"compression\"\n",
)
.expect("parse");
assert_eq!(m.mcp.expect("wiring").integration, "compression");
}
#[test]
fn categories_derive_an_adapter_leniently() {
let derived = parse(
"[addon]\nname = \"x\"\ncategories = [\"workflow\", \"memory\"]\n[mcp]\ncommand = \"c\"\n",
)
.expect("parse");
assert_eq!(derived.mcp.expect("wiring").integration, "memory");
let none = parse(
"[addon]\nname = \"x\"\ncategories = [\"plans\", \"workflow\"]\n[mcp]\ncommand = \"c\"\n",
)
.expect("unknown categories are not an error");
assert_eq!(none.mcp.expect("wiring").integration, "");
}
#[test]
fn an_unknown_integration_is_refused_and_a_known_one_canonicalises() {
let err =
parse("[addon]\nname = \"x\"\n[mcp]\ncommand = \"c\"\nintegration = \"code_grah\"\n")
.expect_err("must refuse");
assert!(err.contains("unknown integration"), "{err}");
for (written, canonical) in [
("repomix", "codebase-pack"),
("callgraph", "code-graph"),
("compressor", "compression"),
("none", "none"),
] {
let m = parse(&format!(
"[addon]\nname = \"x\"\n[mcp]\ncommand = \"c\"\nintegration = \"{written}\"\n"
))
.expect("parse");
assert_eq!(m.mcp.expect("wiring").integration, canonical);
}
let m = parse("[addon]\nname = \"x\"\n[mcp]\ncommand = \"c\"\n").expect("parse");
assert_eq!(m.mcp.expect("wiring").integration, "");
}
#[test]
fn half_configured_wiring_is_refused() {
let cases = [
(
"[addon]\nname = \"x\"\n[mcp]\ntransport = \"stdio\"\n",
"requires `command`",
),
(
"[addon]\nname = \"x\"\n[mcp]\ntransport = \"http\"\n",
"requires `url`",
),
(
"[addon]\nname = \"x\"\n[mcp]\ntransport = \"http\"\nurl = \"ftp://nope\"\n",
"must be http(s)",
),
(
"[addon]\nname = \"x\"\n[mcp]\ntransport = \"carrier-pigeon\"\n",
"unknown transport",
),
("[addon]\nversion = \"1\"\n", "name is required"),
("[mcp]\ncommand = \"x\"\n", "missing [addon]"),
];
for (text, expected) in cases {
let err = parse(text).expect_err("must refuse");
assert!(err.contains(expected), "expected {expected:?} in {err:?}");
}
}
}