Mark NEAR AI companion MCP as derived

This commit is contained in:
Coffee
2026-03-17 14:04:16 +08:00
parent 8a5346f417
commit 8bcdf1608f
7 changed files with 57 additions and 3 deletions
+1
View File
@@ -68,6 +68,7 @@ pub async fn extensions_list_handler(
tools: ext.tools,
needs_setup: ext.needs_setup,
has_auth: ext.has_auth,
derived: ext.derived,
activation_status,
activation_error: ext.activation_error,
version: ext.version,
+3
View File
@@ -1885,6 +1885,7 @@ async fn extensions_list_handler(
tools: ext.tools,
needs_setup: ext.needs_setup,
has_auth: ext.has_auth,
derived: ext.derived,
activation_status,
activation_error: ext.activation_error,
version: ext.version,
@@ -2795,6 +2796,7 @@ mod tests {
tools: Vec::new(),
needs_setup: true,
has_auth: false,
derived: false,
installed: true,
activation_error: None,
version: None,
@@ -2832,6 +2834,7 @@ mod tests {
tools: Vec::new(),
needs_setup: true,
has_auth: false,
derived: false,
installed: true,
activation_error: None,
version: None,
+3
View File
@@ -462,6 +462,9 @@ pub struct ExtensionInfo {
/// Whether this extension has an auth configuration (OAuth or manual token).
#[serde(default)]
pub has_auth: bool,
/// Whether this extension is derived from runtime/provider state.
#[serde(default)]
pub derived: bool,
/// WASM channel activation status.
#[serde(skip_serializing_if = "Option::is_none")]
pub activation_status: Option<ExtensionActivationStatus>,
+25 -3
View File
@@ -281,6 +281,13 @@ async fn add_server(args: McpAddArgs) -> anyhow::Result<()> {
/// Remove an MCP server.
async fn remove_server(name: String) -> anyhow::Result<()> {
if config::is_nearai_companion_server_name(&name) {
anyhow::bail!(
"Server '{}' is derived from the active NEAR AI provider and cannot be removed directly",
name
);
}
let db = connect_db().await;
let mut servers = load_servers(db.as_deref()).await?;
if !servers.remove(&name) {
@@ -611,6 +618,13 @@ async fn test_server(name: String, user_id: String) -> anyhow::Result<()> {
/// Toggle server enabled/disabled state.
async fn toggle_server(name: String, enable: bool, disable: bool) -> anyhow::Result<()> {
if config::is_nearai_companion_server_name(&name) {
anyhow::bail!(
"Server '{}' is derived from the active NEAR AI provider and cannot be toggled directly",
name
);
}
let db = connect_db().await;
let mut servers = load_servers(db.as_deref()).await?;
@@ -647,11 +661,19 @@ async fn connect_db() -> Option<Arc<dyn Database>> {
/// Load MCP servers (DB if available, else disk).
async fn load_servers(db: Option<&dyn Database>) -> Result<McpServersFile, config::ConfigError> {
if let Some(db) = db {
config::load_mcp_servers_from_db(db, DEFAULT_USER_ID).await
let mut servers = if let Some(db) = db {
config::load_mcp_servers_from_db(db, DEFAULT_USER_ID).await?
} else {
config::load_mcp_servers().await
config::load_mcp_servers().await?
};
if let Ok(cfg) = Config::from_env().await
&& let Some(companion) = config::derive_nearai_companion_mcp_server(&cfg)
{
servers.insert_if_absent(companion);
}
Ok(servers)
}
/// Save MCP servers (DB if available, else disk).
+13
View File
@@ -1055,6 +1055,9 @@ impl ExtensionManager {
tools,
needs_setup: false,
has_auth: server.requires_auth(),
derived: crate::tools::mcp::config::is_nearai_companion_server_name(
&server.name,
),
installed: true,
activation_error: None,
version: None,
@@ -1106,6 +1109,7 @@ impl ExtensionManager {
tools: if active { vec![name] } else { Vec::new() },
needs_setup: auth_state == ToolAuthState::NeedsSetup,
has_auth: auth_state != ToolAuthState::NoAuth,
derived: false,
installed: true,
activation_error: None,
version,
@@ -1162,6 +1166,7 @@ impl ExtensionManager {
tools: Vec::new(),
needs_setup: auth_state == ToolAuthState::NeedsSetup,
has_auth: auth_state != ToolAuthState::NoAuth,
derived: false,
installed: true,
activation_error,
version,
@@ -1202,6 +1207,7 @@ impl ExtensionManager {
tools: Vec::new(),
needs_setup: false,
has_auth: true,
derived: false,
installed: true,
activation_error: None,
version: None,
@@ -1236,6 +1242,7 @@ impl ExtensionManager {
tools: Vec::new(),
needs_setup: false,
has_auth: false,
derived: false,
installed: false,
activation_error: None,
version: entry.version,
@@ -1266,6 +1273,12 @@ impl ExtensionManager {
match kind {
ExtensionKind::McpServer => {
if crate::tools::mcp::config::is_nearai_companion_server_name(name) {
return Err(ExtensionError::Config(
"This MCP server is derived from the active NEAR AI provider and cannot be removed directly".to_string(),
));
}
// Unregister tools with this server's prefix
let tool_names: Vec<String> = self
.tool_registry
+8
View File
@@ -504,6 +504,10 @@ pub struct InstalledExtension {
/// Whether this extension has an auth configuration (OAuth or manual token).
#[serde(default)]
pub has_auth: bool,
/// Whether this extension is derived from provider/runtime state instead of
/// being a user-managed persisted configuration.
#[serde(default)]
pub derived: bool,
/// Whether this extension is installed locally (false = available in registry but not installed).
#[serde(default = "default_true")]
pub installed: bool,
@@ -934,6 +938,7 @@ mod tests {
assert!(ext.installed, "installed should default to true");
assert!(!ext.needs_setup, "needs_setup should default to false");
assert!(!ext.has_auth);
assert!(!ext.derived);
assert!(ext.tools.is_empty());
assert!(ext.display_name.is_none());
assert!(ext.description.is_none());
@@ -954,6 +959,7 @@ mod tests {
tools: vec!["send_email".to_string(), "read_inbox".to_string()],
needs_setup: true,
has_auth: true,
derived: true,
installed: false,
activation_error: Some("token expired".to_string()),
version: None,
@@ -963,6 +969,7 @@ mod tests {
assert_eq!(json["description"], "Read and send emails");
assert_eq!(json["url"], "https://gmail.example.com");
assert_eq!(json["needs_setup"], true);
assert_eq!(json["derived"], true);
assert_eq!(json["installed"], false);
assert_eq!(json["activation_error"], "token expired");
@@ -970,6 +977,7 @@ mod tests {
assert_eq!(back.name, "gmail");
assert_eq!(back.tools.len(), 2);
assert!(back.needs_setup);
assert!(back.derived);
assert!(!back.installed);
assert_eq!(back.activation_error.as_deref(), Some("token expired"));
}
+4
View File
@@ -292,6 +292,10 @@ impl McpServerConfig {
/// Reserved name used for the companion chat-api MCP server derived from NEAR AI config.
pub const NEARAI_COMPANION_MCP_NAME: &str = "_nearai_companion_mcp";
pub fn is_nearai_companion_server_name(name: &str) -> bool {
name == NEARAI_COMPANION_MCP_NAME
}
/// Build the companion chat-api MCP server from the active NearAI config.
///
/// The MCP endpoint is treated as a sibling to the versioned REST API: