use crate::error::McpResult;
use aimdb_client::{self, connection::AimxClient};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use tracing::debug;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DiscoveredInstance {
pub socket_path: String,
pub server_version: String,
pub protocol_version: String,
pub permissions: Vec<String>,
pub writable_records: Vec<String>,
pub max_subscriptions: Option<usize>,
pub authenticated: bool,
}
pub async fn discover_instances(_args: Option<Value>) -> McpResult<Value> {
debug!("🔍 Discovering AimDB instances...");
let instances = aimdb_client::discover_instances().await?;
debug!("✅ Found {} instance(s)", instances.len());
let discovered: Vec<DiscoveredInstance> = instances
.into_iter()
.map(|info| DiscoveredInstance {
socket_path: info.socket_path.display().to_string(),
server_version: info.server_version,
protocol_version: info.protocol_version,
permissions: info.permissions,
writable_records: info.writable_records,
max_subscriptions: info.max_subscriptions,
authenticated: info.authenticated,
})
.collect();
let result = serde_json::to_value(discovered)?;
Ok(result)
}
#[derive(Debug, Deserialize)]
struct GetInstanceInfoParams {
socket_path: Option<String>,
}
#[derive(Debug, Serialize)]
pub struct InstanceInfoResult {
pub socket_path: String,
pub server_version: String,
pub protocol_version: String,
pub permissions: Vec<String>,
pub writable_records: Vec<String>,
pub max_subscriptions: Option<usize>,
pub authenticated: bool,
}
pub async fn get_instance_info(args: Option<Value>) -> McpResult<Value> {
let params: GetInstanceInfoParams = match args {
Some(value) => serde_json::from_value(value)?,
None => {
return Err(crate::error::McpError::InvalidParams(
"Missing socket_path".into(),
))
}
};
let socket_path = super::resolve_socket_path(params.socket_path)?;
debug!("🔍 Getting instance info for: {}", socket_path);
let client = if let Some(pool) = super::connection_pool() {
pool.get_connection(&socket_path).await?
} else {
AimxClient::connect(&socket_path).await?
};
let server_info = client.server_info();
let result = InstanceInfoResult {
socket_path: socket_path.clone(),
server_version: server_info.server.clone(),
protocol_version: server_info.version.clone(),
permissions: server_info.permissions.clone(),
writable_records: server_info.writable_records.clone(),
max_subscriptions: server_info.max_subscriptions,
authenticated: server_info.authenticated.unwrap_or(false),
};
debug!("✅ Retrieved instance info: {:?}", result);
Ok(serde_json::to_value(result)?)
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_discover_instances() {
let result = discover_instances(None).await;
match result {
Ok(value) => {
assert!(value.is_array());
println!("Found {} instance(s)", value.as_array().unwrap().len());
}
Err(err) => {
assert!(err.message().contains("No running AimDB instances"));
}
}
}
#[tokio::test]
async fn test_get_instance_info_missing_params() {
let result = get_instance_info(None).await;
assert!(result.is_err());
let err = result.unwrap_err();
assert!(err.message().contains("Missing socket_path"));
}
#[tokio::test]
async fn test_get_instance_info_invalid_socket() {
let params = serde_json::json!({
"socket_path": "/tmp/nonexistent.sock"
});
let result = get_instance_info(Some(params)).await;
assert!(result.is_err());
}
}