use redis_cloud::acl::{
AclRedisRuleCreateRequest, AclRedisRuleUpdateRequest, AclRoleCreateRequest,
AclRoleDatabaseSpec, AclRoleRedisRuleSpec, AclRoleUpdateRequest, AclUserCreateRequest,
AclUserUpdateRequest,
};
use redis_cloud::cloud_accounts::{CloudAccountCreateRequest, CloudAccountUpdateRequest};
use redis_cloud::users::AccountUserUpdateRequest;
use redis_cloud::{
AccountHandler, AclHandler, CloudAccountHandler, CostReportCreateRequest, CostReportHandler,
TaskHandler, UserHandler,
};
use tower_mcp::{CallToolResult, ResultExt};
use crate::tools::macros::{cloud_tool, mcp_module};
#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
pub struct DatabaseSpec {
pub subscription_id: i32,
pub database_id: i32,
#[serde(default)]
pub regions: Option<Vec<String>>,
}
#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
pub struct RedisRuleSpec {
pub rule_name: String,
pub databases: Vec<DatabaseSpec>,
}
fn default_task_timeout() -> u64 {
300
}
fn default_task_interval() -> u64 {
5
}
mcp_module! {
get_account => "get_account",
get_system_logs => "get_system_logs",
get_session_logs => "get_session_logs",
get_regions => "get_regions",
get_modules => "get_modules",
list_account_users => "list_account_users",
get_account_user => "get_account_user",
update_account_user => "update_account_user",
delete_account_user => "delete_account_user",
list_acl_users => "list_acl_users",
get_acl_user => "get_acl_user",
list_acl_roles => "list_acl_roles",
list_redis_rules => "list_redis_rules",
create_acl_user => "create_acl_user",
update_acl_user => "update_acl_user",
delete_acl_user => "delete_acl_user",
create_acl_role => "create_acl_role",
update_acl_role => "update_acl_role",
delete_acl_role => "delete_acl_role",
create_redis_rule => "create_redis_rule",
update_redis_rule => "update_redis_rule",
delete_redis_rule => "delete_redis_rule",
generate_cost_report => "generate_cost_report",
download_cost_report => "download_cost_report",
list_payment_methods => "list_payment_methods",
list_tasks => "list_tasks",
get_task => "get_task",
wait_for_cloud_task => "wait_for_cloud_task",
list_cloud_accounts => "list_cloud_accounts",
get_cloud_account => "get_cloud_account",
create_cloud_account => "create_cloud_account",
update_cloud_account => "update_cloud_account",
delete_cloud_account => "delete_cloud_account",
}
cloud_tool!(read_only, get_account, "get_account",
"Get current account information.",
{} => |client, _input| {
let handler = AccountHandler::new(client);
let account = handler
.get_current_account()
.await
.tool_context("Failed to get account")?;
CallToolResult::from_serialize(&account)
}
);
cloud_tool!(read_only, get_system_logs, "get_system_logs",
"Get system audit logs.",
{
#[serde(default)]
pub offset: Option<i32>,
#[serde(default)]
pub limit: Option<i32>,
} => |client, input| {
let handler = AccountHandler::new(client);
let logs = handler
.get_account_system_logs(input.offset, input.limit)
.await
.tool_context("Failed to get system logs")?;
CallToolResult::from_serialize(&logs)
}
);
cloud_tool!(read_only, get_session_logs, "get_session_logs",
"Get session activity logs.",
{
#[serde(default)]
pub offset: Option<i32>,
#[serde(default)]
pub limit: Option<i32>,
} => |client, input| {
let handler = AccountHandler::new(client);
let logs = handler
.get_account_session_logs(input.offset, input.limit)
.await
.tool_context("Failed to get session logs")?;
CallToolResult::from_serialize(&logs)
}
);
cloud_tool!(read_only, get_regions, "get_regions",
"Get supported cloud regions. Optionally filter by provider.",
{
#[serde(default)]
pub provider: Option<String>,
} => |client, input| {
let handler = AccountHandler::new(client);
let regions = handler
.get_supported_regions(input.provider)
.await
.tool_context("Failed to get regions")?;
CallToolResult::from_serialize(®ions)
}
);
cloud_tool!(read_only, get_modules, "get_modules",
"Get supported database modules.",
{} => |client, _input| {
let handler = AccountHandler::new(client);
let modules = handler
.get_supported_database_modules()
.await
.tool_context("Failed to get modules")?;
CallToolResult::from_serialize(&modules)
}
);
cloud_tool!(read_only, list_account_users, "list_account_users",
"List all account users (team members with console access).",
{} => |client, _input| {
let handler = UserHandler::new(client);
let users = handler
.get_all_users()
.await
.tool_context("Failed to list users")?;
CallToolResult::from_serialize(&users)
}
);
cloud_tool!(read_only, get_account_user, "get_account_user",
"Get an account user by ID.",
{
pub user_id: i32,
} => |client, input| {
let handler = UserHandler::new(client);
let user = handler
.get_user_by_id(input.user_id)
.await
.tool_context("Failed to get account user")?;
CallToolResult::from_serialize(&user)
}
);
cloud_tool!(write, update_account_user, "update_account_user",
"Update an account user's name or role.",
{
pub user_id: i32,
pub name: String,
#[serde(default)]
pub role: Option<String>,
} => |client, input| {
let handler = UserHandler::new(client);
let request = AccountUserUpdateRequest {
user_id: None,
name: input.name,
role: input.role,
command_type: None,
};
let result = handler
.update_user(input.user_id, &request)
.await
.tool_context("Failed to update account user")?;
CallToolResult::from_serialize(&result)
}
);
cloud_tool!(destructive, delete_account_user, "delete_account_user",
"DANGEROUS: Delete an account user. The user will lose all access.",
{
pub user_id: i32,
} => |client, input| {
let handler = UserHandler::new(client);
let result = handler
.delete_user_by_id(input.user_id)
.await
.tool_context("Failed to delete account user")?;
CallToolResult::from_serialize(&result)
}
);
cloud_tool!(read_only, list_acl_users, "list_acl_users",
"List all ACL users.",
{} => |client, _input| {
let handler = AclHandler::new(client);
let users = handler
.get_all_acl_users()
.await
.tool_context("Failed to list ACL users")?;
CallToolResult::from_serialize(&users)
}
);
cloud_tool!(read_only, get_acl_user, "get_acl_user",
"Get an ACL user by ID.",
{
pub user_id: i32,
} => |client, input| {
let handler = AclHandler::new(client);
let user = handler
.get_user_by_id(input.user_id)
.await
.tool_context("Failed to get ACL user")?;
CallToolResult::from_serialize(&user)
}
);
cloud_tool!(read_only, list_acl_roles, "list_acl_roles",
"List all ACL roles.",
{} => |client, _input| {
let handler = AclHandler::new(client);
let roles = handler
.get_roles()
.await
.tool_context("Failed to list ACL roles")?;
CallToolResult::from_serialize(&roles)
}
);
cloud_tool!(read_only, list_redis_rules, "list_redis_rules",
"List all Redis ACL rules.",
{} => |client, _input| {
let handler = AclHandler::new(client);
let rules = handler
.get_all_redis_rules()
.await
.tool_context("Failed to list Redis rules")?;
CallToolResult::from_serialize(&rules)
}
);
cloud_tool!(write, create_acl_user, "create_acl_user",
"Create a new ACL user with a database access role.",
{
pub name: String,
pub role: String,
pub password: String,
} => |client, input| {
let handler = AclHandler::new(client);
let request = AclUserCreateRequest {
name: input.name,
role: input.role,
password: input.password,
command_type: None,
};
let result = handler
.create_user(&request)
.await
.tool_context("Failed to create ACL user")?;
CallToolResult::from_serialize(&result)
}
);
cloud_tool!(write, update_acl_user, "update_acl_user",
"Update an ACL user's role or password.",
{
pub user_id: i32,
#[serde(default)]
pub role: Option<String>,
#[serde(default)]
pub password: Option<String>,
} => |client, input| {
let handler = AclHandler::new(client);
let request = AclUserUpdateRequest {
user_id: None,
role: input.role,
password: input.password,
command_type: None,
};
let result = handler
.update_acl_user(input.user_id, &request)
.await
.tool_context("Failed to update ACL user")?;
CallToolResult::from_serialize(&result)
}
);
cloud_tool!(destructive, delete_acl_user, "delete_acl_user",
"DANGEROUS: Delete an ACL user. Active sessions will be terminated.",
{
pub user_id: i32,
} => |client, input| {
let handler = AclHandler::new(client);
let result = handler
.delete_user(input.user_id)
.await
.tool_context("Failed to delete ACL user")?;
CallToolResult::from_serialize(&result)
}
);
cloud_tool!(write, create_acl_role, "create_acl_role",
"Create a new ACL role with Redis rules and database associations.",
{
pub name: String,
pub redis_rules: Vec<RedisRuleSpec>,
} => |client, input| {
let handler = AclHandler::new(client);
let request = AclRoleCreateRequest {
name: input.name,
redis_rules: input
.redis_rules
.into_iter()
.map(|r| AclRoleRedisRuleSpec {
rule_name: r.rule_name,
databases: r
.databases
.into_iter()
.map(|d| AclRoleDatabaseSpec {
subscription_id: d.subscription_id,
database_id: d.database_id,
regions: d.regions,
})
.collect(),
})
.collect(),
command_type: None,
};
let result = handler
.create_role(&request)
.await
.tool_context("Failed to create ACL role")?;
CallToolResult::from_serialize(&result)
}
);
cloud_tool!(write, update_acl_role, "update_acl_role",
"Update an ACL role's name or Redis rule assignments.",
{
pub role_id: i32,
#[serde(default)]
pub name: Option<String>,
#[serde(default)]
pub redis_rules: Option<Vec<RedisRuleSpec>>,
} => |client, input| {
let handler = AclHandler::new(client);
let request = AclRoleUpdateRequest {
name: input.name,
redis_rules: input.redis_rules.map(|rules| {
rules
.into_iter()
.map(|r| AclRoleRedisRuleSpec {
rule_name: r.rule_name,
databases: r
.databases
.into_iter()
.map(|d| AclRoleDatabaseSpec {
subscription_id: d.subscription_id,
database_id: d.database_id,
regions: d.regions,
})
.collect(),
})
.collect()
}),
role_id: None,
command_type: None,
};
let result = handler
.update_role(input.role_id, &request)
.await
.tool_context("Failed to update ACL role")?;
CallToolResult::from_serialize(&result)
}
);
cloud_tool!(destructive, delete_acl_role, "delete_acl_role",
"DANGEROUS: Delete an ACL role. Assigned users will lose their permissions.",
{
pub role_id: i32,
} => |client, input| {
let handler = AclHandler::new(client);
let result = handler
.delete_acl_role(input.role_id)
.await
.tool_context("Failed to delete ACL role")?;
CallToolResult::from_serialize(&result)
}
);
cloud_tool!(write, create_redis_rule, "create_redis_rule",
"Create a new Redis ACL rule defining command permissions.",
{
pub name: String,
pub redis_rule: String,
} => |client, input| {
let handler = AclHandler::new(client);
let request = AclRedisRuleCreateRequest {
name: input.name,
redis_rule: input.redis_rule,
command_type: None,
};
let result = handler
.create_redis_rule(&request)
.await
.tool_context("Failed to create Redis rule")?;
CallToolResult::from_serialize(&result)
}
);
cloud_tool!(write, update_redis_rule, "update_redis_rule",
"Update a Redis ACL rule's name or pattern.",
{
pub rule_id: i32,
pub name: String,
pub redis_rule: String,
} => |client, input| {
let handler = AclHandler::new(client);
let request = AclRedisRuleUpdateRequest {
redis_rule_id: None,
name: input.name,
redis_rule: input.redis_rule,
command_type: None,
};
let result = handler
.update_redis_rule(input.rule_id, &request)
.await
.tool_context("Failed to update Redis rule")?;
CallToolResult::from_serialize(&result)
}
);
cloud_tool!(destructive, delete_redis_rule, "delete_redis_rule",
"DANGEROUS: Delete a Redis ACL rule. Roles using it will lose those permissions.",
{
pub rule_id: i32,
} => |client, input| {
let handler = AclHandler::new(client);
let result = handler
.delete_redis_rule(input.rule_id)
.await
.tool_context("Failed to delete Redis rule")?;
CallToolResult::from_serialize(&result)
}
);
cloud_tool!(write, generate_cost_report, "generate_cost_report",
"Generate a FOCUS cost report for the specified date range.",
{
pub start_date: String,
pub end_date: String,
#[serde(default)]
pub format: Option<String>,
#[serde(default)]
pub subscription_ids: Option<Vec<i32>>,
#[serde(default)]
pub database_ids: Option<Vec<i32>>,
#[serde(default)]
pub subscription_type: Option<String>,
#[serde(default)]
pub regions: Option<Vec<String>>,
} => |client, input| {
use redis_cloud::{CostReportFormat, SubscriptionType};
let handler = CostReportHandler::new(client);
let format = input.format.as_deref().map(|f| match f {
"json" => CostReportFormat::Json,
_ => CostReportFormat::Csv,
});
let subscription_type = input.subscription_type.as_deref().map(|t| match t {
"essentials" => SubscriptionType::Essentials,
_ => SubscriptionType::Pro,
});
let request = CostReportCreateRequest {
start_date: input.start_date,
end_date: input.end_date,
format,
subscription_ids: input.subscription_ids,
database_ids: input.database_ids,
subscription_type,
regions: input.regions,
tags: None,
};
let result = handler
.generate_cost_report(request)
.await
.tool_context("Failed to generate cost report")?;
CallToolResult::from_serialize(&result)
}
);
cloud_tool!(read_only, download_cost_report, "download_cost_report",
"Download a previously generated cost report by ID.",
{
pub cost_report_id: String,
} => |client, input| {
let handler = CostReportHandler::new(client);
let bytes = handler
.download_cost_report(&input.cost_report_id)
.await
.tool_context("Failed to download cost report")?;
let content = String::from_utf8(bytes).unwrap_or_else(|e| {
format!("<binary data, {} bytes>", e.into_bytes().len())
});
CallToolResult::from_serialize(&content)
}
);
cloud_tool!(read_only, list_payment_methods, "list_payment_methods",
"List all payment methods.",
{} => |client, _input| {
let handler = AccountHandler::new(client);
let methods = handler
.get_account_payment_methods()
.await
.tool_context("Failed to list payment methods")?;
CallToolResult::from_serialize(&methods)
}
);
cloud_tool!(read_only, list_tasks, "list_tasks",
"List all async tasks.",
{} => |client, _input| {
let handler = TaskHandler::new(client);
let tasks = handler
.get_all_tasks()
.await
.tool_context("Failed to list tasks")?;
CallToolResult::from_list("tasks", &tasks)
}
);
cloud_tool!(read_only, get_task, "get_task",
"Get task status by ID.",
{
pub task_id: String,
} => |client, input| {
let handler = TaskHandler::new(client);
let task = handler
.get_task_by_id(input.task_id)
.await
.tool_context("Failed to get task")?;
CallToolResult::from_serialize(&task)
}
);
cloud_tool!(read_only, wait_for_cloud_task, "wait_for_cloud_task",
"Poll an async task until it reaches a terminal state. Useful for multi-step workflows.",
{
pub task_id: String,
#[serde(default = "default_task_timeout")]
pub timeout_seconds: u64,
#[serde(default = "default_task_interval")]
pub interval_seconds: u64,
} => |client, input| {
let handler = TaskHandler::new(client);
let deadline =
tokio::time::Instant::now() + std::time::Duration::from_secs(input.timeout_seconds);
let interval = std::time::Duration::from_secs(input.interval_seconds);
loop {
let task = handler
.get_task_by_id(input.task_id.clone())
.await
.tool_context("Failed to get task status")?;
if let Some(ref status) = task.status {
let s = status.to_lowercase();
if matches!(
s.as_str(),
"completed"
| "failed"
| "error"
| "success"
| "cancelled"
| "aborted"
) {
return CallToolResult::from_serialize(&task);
}
}
if tokio::time::Instant::now() >= deadline {
return CallToolResult::from_serialize(&serde_json::json!({
"timeout": true,
"message": format!(
"Task {} did not complete within {} seconds",
input.task_id, input.timeout_seconds
),
"last_status": task,
}));
}
tokio::time::sleep(interval).await;
}
}
);
cloud_tool!(read_only, list_cloud_accounts, "list_cloud_accounts",
"List all cloud provider accounts (BYOC).",
{} => |client, _input| {
let handler = CloudAccountHandler::new(client);
let result = handler
.get_cloud_accounts()
.await
.tool_context("Failed to list cloud accounts")?;
let accounts = result.cloud_accounts.unwrap_or_default();
CallToolResult::from_list("cloud_accounts", &accounts)
}
);
cloud_tool!(read_only, get_cloud_account, "get_cloud_account",
"Get a cloud provider account (BYOC) by ID.",
{
pub cloud_account_id: i32,
} => |client, input| {
let handler = CloudAccountHandler::new(client);
let account = handler
.get_cloud_account_by_id(input.cloud_account_id)
.await
.tool_context("Failed to get cloud account")?;
CallToolResult::from_serialize(&account)
}
);
cloud_tool!(write, create_cloud_account, "create_cloud_account",
"Create a new cloud provider account (BYOC).",
{
pub name: String,
#[serde(default)]
pub provider: Option<String>,
pub access_key_id: String,
pub access_secret_key: String,
pub console_username: String,
pub console_password: String,
pub sign_in_login_url: String,
} => |client, input| {
let handler = CloudAccountHandler::new(client);
let request = CloudAccountCreateRequest {
name: input.name,
provider: input.provider,
access_key_id: input.access_key_id,
access_secret_key: input.access_secret_key,
console_username: input.console_username,
console_password: input.console_password,
sign_in_login_url: input.sign_in_login_url,
command_type: None,
};
let result = handler
.create_cloud_account(&request)
.await
.tool_context("Failed to create cloud account")?;
CallToolResult::from_serialize(&result)
}
);
cloud_tool!(write, update_cloud_account, "update_cloud_account",
"Update a cloud provider account (BYOC) configuration.",
{
pub cloud_account_id: i32,
#[serde(default)]
pub name: Option<String>,
pub access_key_id: String,
pub access_secret_key: String,
pub console_username: String,
pub console_password: String,
#[serde(default)]
pub sign_in_login_url: Option<String>,
} => |client, input| {
let handler = CloudAccountHandler::new(client);
let request = CloudAccountUpdateRequest {
name: input.name,
cloud_account_id: None,
access_key_id: input.access_key_id,
access_secret_key: input.access_secret_key,
console_username: input.console_username,
console_password: input.console_password,
sign_in_login_url: input.sign_in_login_url,
command_type: None,
};
let result = handler
.update_cloud_account(input.cloud_account_id, &request)
.await
.tool_context("Failed to update cloud account")?;
CallToolResult::from_serialize(&result)
}
);
cloud_tool!(destructive, delete_cloud_account, "delete_cloud_account",
"DANGEROUS: Delete a cloud provider account (BYOC).",
{
pub cloud_account_id: i32,
} => |client, input| {
let handler = CloudAccountHandler::new(client);
let result = handler
.delete_cloud_account(input.cloud_account_id)
.await
.tool_context("Failed to delete cloud account")?;
CallToolResult::from_serialize(&result)
}
);