use std::sync::Arc;
use redis_cloud::flexible::{DatabaseHandler, SubscriptionHandler};
use redis_cloud::{AccountHandler, AclHandler, TaskHandler, UserHandler};
use schemars::JsonSchema;
use serde::Deserialize;
use tower_mcp::{CallToolResult, Tool, ToolBuilder, ToolError};
use crate::state::AppState;
#[derive(Debug, Deserialize, JsonSchema)]
pub struct ListSubscriptionsInput {}
pub fn list_subscriptions(state: Arc<AppState>) -> Tool {
ToolBuilder::new("list_subscriptions")
.description("List all Redis Cloud subscriptions accessible with the current credentials. Returns JSON with subscription details.")
.read_only()
.idempotent()
.handler_with_state(state, |state, _input: ListSubscriptionsInput| async move {
let client = state
.cloud_client()
.await
.map_err(|e| ToolError::new(format!("Failed to get Cloud client: {}", e)))?;
let handler = SubscriptionHandler::new(client);
let account_subs = handler
.get_all_subscriptions()
.await
.map_err(|e| ToolError::new(format!("Failed to list subscriptions: {}", e)))?;
let output = serde_json::to_string_pretty(&account_subs)
.map_err(|e| ToolError::new(format!("Failed to serialize: {}", e)))?;
Ok(CallToolResult::text(output))
})
.build()
.expect("valid tool")
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct GetSubscriptionInput {
pub subscription_id: i32,
}
pub fn get_subscription(state: Arc<AppState>) -> Tool {
ToolBuilder::new("get_subscription")
.description("Get detailed information about a specific Redis Cloud subscription. Returns JSON with full subscription details.")
.read_only()
.idempotent()
.handler_with_state(state, |state, input: GetSubscriptionInput| async move {
let client = state
.cloud_client()
.await
.map_err(|e| ToolError::new(format!("Failed to get Cloud client: {}", e)))?;
let handler = SubscriptionHandler::new(client);
let subscription = handler
.get_subscription_by_id(input.subscription_id)
.await
.map_err(|e| ToolError::new(format!("Failed to get subscription: {}", e)))?;
let output = serde_json::to_string_pretty(&subscription)
.map_err(|e| ToolError::new(format!("Failed to serialize: {}", e)))?;
Ok(CallToolResult::text(output))
})
.build()
.expect("valid tool")
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct ListDatabasesInput {
pub subscription_id: i32,
}
pub fn list_databases(state: Arc<AppState>) -> Tool {
ToolBuilder::new("list_databases")
.description(
"List all databases in a Redis Cloud subscription. Returns JSON with database details.",
)
.read_only()
.idempotent()
.handler_with_state(state, |state, input: ListDatabasesInput| async move {
let client = state
.cloud_client()
.await
.map_err(|e| ToolError::new(format!("Failed to get Cloud client: {}", e)))?;
let handler = DatabaseHandler::new(client);
let databases = handler
.get_subscription_databases(input.subscription_id, None, None)
.await
.map_err(|e| ToolError::new(format!("Failed to list databases: {}", e)))?;
let output = serde_json::to_string_pretty(&databases)
.map_err(|e| ToolError::new(format!("Failed to serialize: {}", e)))?;
Ok(CallToolResult::text(output))
})
.build()
.expect("valid tool")
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct GetDatabaseInput {
pub subscription_id: i32,
pub database_id: i32,
}
pub fn get_database(state: Arc<AppState>) -> Tool {
ToolBuilder::new("get_database")
.description("Get detailed information about a specific Redis Cloud database. Returns JSON with full database configuration.")
.read_only()
.idempotent()
.handler_with_state(state, |state, input: GetDatabaseInput| async move {
let client = state
.cloud_client()
.await
.map_err(|e| ToolError::new(format!("Failed to get Cloud client: {}", e)))?;
let handler = DatabaseHandler::new(client);
let database = handler
.get_subscription_database_by_id(input.subscription_id, input.database_id)
.await
.map_err(|e| ToolError::new(format!("Failed to get database: {}", e)))?;
let output = serde_json::to_string_pretty(&database)
.map_err(|e| ToolError::new(format!("Failed to serialize: {}", e)))?;
Ok(CallToolResult::text(output))
})
.build()
.expect("valid tool")
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct GetAccountInput {}
pub fn get_account(state: Arc<AppState>) -> Tool {
ToolBuilder::new("get_account")
.description("Get information about the current Redis Cloud account including name, ID, and settings.")
.read_only()
.idempotent()
.handler_with_state(state, |state, _input: GetAccountInput| async move {
let client = state
.cloud_client()
.await
.map_err(|e| ToolError::new(format!("Failed to get Cloud client: {}", e)))?;
let handler = AccountHandler::new(client);
let account = handler
.get_current_account()
.await
.map_err(|e| ToolError::new(format!("Failed to get account: {}", e)))?;
let output = serde_json::to_string_pretty(&account)
.map_err(|e| ToolError::new(format!("Failed to serialize: {}", e)))?;
Ok(CallToolResult::text(output))
})
.build()
.expect("valid tool")
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct GetRegionsInput {
#[serde(default)]
pub provider: Option<String>,
}
pub fn get_regions(state: Arc<AppState>) -> Tool {
ToolBuilder::new("get_regions")
.description(
"Get supported cloud regions for Redis Cloud. Optionally filter by provider (AWS, GCP, Azure).",
)
.read_only()
.idempotent()
.handler_with_state(state, |state, input: GetRegionsInput| async move {
let client = state
.cloud_client()
.await
.map_err(|e| ToolError::new(format!("Failed to get Cloud client: {}", e)))?;
let handler = AccountHandler::new(client);
let regions = handler
.get_supported_regions(input.provider)
.await
.map_err(|e| ToolError::new(format!("Failed to get regions: {}", e)))?;
let output = serde_json::to_string_pretty(®ions)
.map_err(|e| ToolError::new(format!("Failed to serialize: {}", e)))?;
Ok(CallToolResult::text(output))
})
.build()
.expect("valid tool")
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct GetModulesInput {}
pub fn get_modules(state: Arc<AppState>) -> Tool {
ToolBuilder::new("get_modules")
.description(
"Get supported Redis database modules (e.g., Search, JSON, TimeSeries, Bloom).",
)
.read_only()
.idempotent()
.handler_with_state(state, |state, _input: GetModulesInput| async move {
let client = state
.cloud_client()
.await
.map_err(|e| ToolError::new(format!("Failed to get Cloud client: {}", e)))?;
let handler = AccountHandler::new(client);
let modules = handler
.get_supported_database_modules()
.await
.map_err(|e| ToolError::new(format!("Failed to get modules: {}", e)))?;
let output = serde_json::to_string_pretty(&modules)
.map_err(|e| ToolError::new(format!("Failed to serialize: {}", e)))?;
Ok(CallToolResult::text(output))
})
.build()
.expect("valid tool")
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct ListTasksInput {}
pub fn list_tasks(state: Arc<AppState>) -> Tool {
ToolBuilder::new("list_tasks")
.description("List all async tasks in the Redis Cloud account. Tasks track long-running operations like database creation.")
.read_only()
.idempotent()
.handler_with_state(state, |state, _input: ListTasksInput| async move {
let client = state
.cloud_client()
.await
.map_err(|e| ToolError::new(format!("Failed to get Cloud client: {}", e)))?;
let handler = TaskHandler::new(client);
let tasks = handler
.get_all_tasks()
.await
.map_err(|e| ToolError::new(format!("Failed to list tasks: {}", e)))?;
let output = serde_json::to_string_pretty(&tasks)
.map_err(|e| ToolError::new(format!("Failed to serialize: {}", e)))?;
Ok(CallToolResult::text(output))
})
.build()
.expect("valid tool")
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct GetTaskInput {
pub task_id: String,
}
pub fn get_task(state: Arc<AppState>) -> Tool {
ToolBuilder::new("get_task")
.description("Get status and details of a specific async task by ID.")
.read_only()
.idempotent()
.handler_with_state(state, |state, input: GetTaskInput| async move {
let client = state
.cloud_client()
.await
.map_err(|e| ToolError::new(format!("Failed to get Cloud client: {}", e)))?;
let handler = TaskHandler::new(client);
let task = handler
.get_task_by_id(input.task_id)
.await
.map_err(|e| ToolError::new(format!("Failed to get task: {}", e)))?;
let output = serde_json::to_string_pretty(&task)
.map_err(|e| ToolError::new(format!("Failed to serialize: {}", e)))?;
Ok(CallToolResult::text(output))
})
.build()
.expect("valid tool")
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct ListAccountUsersInput {}
pub fn list_account_users(state: Arc<AppState>) -> Tool {
ToolBuilder::new("list_account_users")
.description(
"List all users in the Redis Cloud account (team members with console access).",
)
.read_only()
.idempotent()
.handler_with_state(state, |state, _input: ListAccountUsersInput| async move {
let client = state
.cloud_client()
.await
.map_err(|e| ToolError::new(format!("Failed to get Cloud client: {}", e)))?;
let handler = UserHandler::new(client);
let users = handler
.get_all_users()
.await
.map_err(|e| ToolError::new(format!("Failed to list users: {}", e)))?;
let output = serde_json::to_string_pretty(&users)
.map_err(|e| ToolError::new(format!("Failed to serialize: {}", e)))?;
Ok(CallToolResult::text(output))
})
.build()
.expect("valid tool")
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct ListAclUsersInput {}
pub fn list_acl_users(state: Arc<AppState>) -> Tool {
ToolBuilder::new("list_acl_users")
.description("List all ACL users (database-level Redis users for authentication).")
.read_only()
.idempotent()
.handler_with_state(state, |state, _input: ListAclUsersInput| async move {
let client = state
.cloud_client()
.await
.map_err(|e| ToolError::new(format!("Failed to get Cloud client: {}", e)))?;
let handler = AclHandler::new(client);
let users = handler
.get_all_acl_users()
.await
.map_err(|e| ToolError::new(format!("Failed to list ACL users: {}", e)))?;
let output = serde_json::to_string_pretty(&users)
.map_err(|e| ToolError::new(format!("Failed to serialize: {}", e)))?;
Ok(CallToolResult::text(output))
})
.build()
.expect("valid tool")
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct ListAclRolesInput {}
pub fn list_acl_roles(state: Arc<AppState>) -> Tool {
ToolBuilder::new("list_acl_roles")
.description("List all ACL roles (permission templates for database access).")
.read_only()
.idempotent()
.handler_with_state(state, |state, _input: ListAclRolesInput| async move {
let client = state
.cloud_client()
.await
.map_err(|e| ToolError::new(format!("Failed to get Cloud client: {}", e)))?;
let handler = AclHandler::new(client);
let roles = handler
.get_roles()
.await
.map_err(|e| ToolError::new(format!("Failed to list ACL roles: {}", e)))?;
let output = serde_json::to_string_pretty(&roles)
.map_err(|e| ToolError::new(format!("Failed to serialize: {}", e)))?;
Ok(CallToolResult::text(output))
})
.build()
.expect("valid tool")
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct ListRedisRulesInput {}
pub fn list_redis_rules(state: Arc<AppState>) -> Tool {
ToolBuilder::new("list_redis_rules")
.description("List all Redis ACL rules (command permissions for Redis users).")
.read_only()
.idempotent()
.handler_with_state(state, |state, _input: ListRedisRulesInput| async move {
let client = state
.cloud_client()
.await
.map_err(|e| ToolError::new(format!("Failed to get Cloud client: {}", e)))?;
let handler = AclHandler::new(client);
let rules = handler
.get_all_redis_rules()
.await
.map_err(|e| ToolError::new(format!("Failed to list Redis rules: {}", e)))?;
let output = serde_json::to_string_pretty(&rules)
.map_err(|e| ToolError::new(format!("Failed to serialize: {}", e)))?;
Ok(CallToolResult::text(output))
})
.build()
.expect("valid tool")
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct GetBackupStatusInput {
pub subscription_id: i32,
pub database_id: i32,
#[serde(default)]
pub region_name: Option<String>,
}
pub fn get_backup_status(state: Arc<AppState>) -> Tool {
ToolBuilder::new("get_backup_status")
.description("Get backup status and history for a Redis Cloud database.")
.read_only()
.idempotent()
.handler_with_state(state, |state, input: GetBackupStatusInput| async move {
let client = state
.cloud_client()
.await
.map_err(|e| ToolError::new(format!("Failed to get Cloud client: {}", e)))?;
let handler = DatabaseHandler::new(client);
let status = handler
.get_database_backup_status(
input.subscription_id,
input.database_id,
input.region_name,
)
.await
.map_err(|e| ToolError::new(format!("Failed to get backup status: {}", e)))?;
let output = serde_json::to_string_pretty(&status)
.map_err(|e| ToolError::new(format!("Failed to serialize: {}", e)))?;
Ok(CallToolResult::text(output))
})
.build()
.expect("valid tool")
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct GetSlowLogInput {
pub subscription_id: i32,
pub database_id: i32,
#[serde(default)]
pub region_name: Option<String>,
}
pub fn get_slow_log(state: Arc<AppState>) -> Tool {
ToolBuilder::new("get_slow_log")
.description(
"Get slow log entries for a Redis Cloud database. Shows slow queries for debugging.",
)
.read_only()
.idempotent()
.handler_with_state(state, |state, input: GetSlowLogInput| async move {
let client = state
.cloud_client()
.await
.map_err(|e| ToolError::new(format!("Failed to get Cloud client: {}", e)))?;
let handler = DatabaseHandler::new(client);
let log = handler
.get_slow_log(input.subscription_id, input.database_id, input.region_name)
.await
.map_err(|e| ToolError::new(format!("Failed to get slow log: {}", e)))?;
let output = serde_json::to_string_pretty(&log)
.map_err(|e| ToolError::new(format!("Failed to serialize: {}", e)))?;
Ok(CallToolResult::text(output))
})
.build()
.expect("valid tool")
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct GetTagsInput {
pub subscription_id: i32,
pub database_id: i32,
}
pub fn get_tags(state: Arc<AppState>) -> Tool {
ToolBuilder::new("get_database_tags")
.description("Get tags attached to a Redis Cloud database.")
.read_only()
.idempotent()
.handler_with_state(state, |state, input: GetTagsInput| async move {
let client = state
.cloud_client()
.await
.map_err(|e| ToolError::new(format!("Failed to get Cloud client: {}", e)))?;
let handler = DatabaseHandler::new(client);
let tags = handler
.get_tags(input.subscription_id, input.database_id)
.await
.map_err(|e| ToolError::new(format!("Failed to get tags: {}", e)))?;
let output = serde_json::to_string_pretty(&tags)
.map_err(|e| ToolError::new(format!("Failed to serialize: {}", e)))?;
Ok(CallToolResult::text(output))
})
.build()
.expect("valid tool")
}