use std::sync::Arc;
use redis_cloud::acl::{
AclRedisRuleCreateRequest, AclRedisRuleUpdateRequest, AclRoleCreateRequest,
AclRoleDatabaseSpec, AclRoleRedisRuleSpec, AclRoleUpdateRequest, AclUserCreateRequest,
AclUserUpdateRequest,
};
use redis_cloud::cloud_accounts::{CloudAccountCreateRequest, CloudAccountUpdateRequest};
use redis_cloud::{
AccountHandler, AclHandler, CloudAccountHandler, CostReportCreateRequest, CostReportHandler,
TaskHandler, UserHandler,
};
use schemars::JsonSchema;
use serde::Deserialize;
use tower_mcp::extract::{Json, State};
use tower_mcp::{CallToolResult, Error as McpError, McpRouter, ResultExt, Tool, ToolBuilder};
use crate::state::AppState;
use crate::tools::wrap_list;
#[derive(Debug, Deserialize, JsonSchema)]
pub struct GetAccountInput {
#[serde(default)]
pub profile: Option<String>,
}
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_safe()
.extractor_handler_typed::<_, _, _, GetAccountInput>(
state,
|State(state): State<Arc<AppState>>, Json(input): Json<GetAccountInput>| async move {
let client = state
.cloud_client_for_profile(input.profile.as_deref())
.await
.map_err(|e| crate::tools::credential_error("cloud", e))?;
let handler = AccountHandler::new(client);
let account = handler
.get_current_account()
.await
.tool_context("Failed to get account")?;
CallToolResult::from_serialize(&account)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct GetSystemLogsInput {
#[serde(default)]
pub offset: Option<i32>,
#[serde(default)]
pub limit: Option<i32>,
#[serde(default)]
pub profile: Option<String>,
}
pub fn get_system_logs(state: Arc<AppState>) -> Tool {
ToolBuilder::new("get_system_logs")
.description(
"Get system audit logs for the Redis Cloud account. Includes events like \
subscription changes, database modifications, and user actions.",
)
.read_only_safe()
.extractor_handler_typed::<_, _, _, GetSystemLogsInput>(
state,
|State(state): State<Arc<AppState>>, Json(input): Json<GetSystemLogsInput>| async move {
let client = state
.cloud_client_for_profile(input.profile.as_deref())
.await
.map_err(|e| crate::tools::credential_error("cloud", e))?;
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)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct GetSessionLogsInput {
#[serde(default)]
pub offset: Option<i32>,
#[serde(default)]
pub limit: Option<i32>,
#[serde(default)]
pub profile: Option<String>,
}
pub fn get_session_logs(state: Arc<AppState>) -> Tool {
ToolBuilder::new("get_session_logs")
.description(
"Get session activity logs for the Redis Cloud account. Includes user login/logout \
events and session information.",
)
.read_only_safe()
.extractor_handler_typed::<_, _, _, GetSessionLogsInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<GetSessionLogsInput>| async move {
let client = state
.cloud_client_for_profile(input.profile.as_deref())
.await
.map_err(|e| crate::tools::credential_error("cloud", e))?;
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)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct GetRegionsInput {
#[serde(default)]
pub provider: Option<String>,
#[serde(default)]
pub profile: 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_safe()
.extractor_handler_typed::<_, _, _, GetRegionsInput>(
state,
|State(state): State<Arc<AppState>>, Json(input): Json<GetRegionsInput>| async move {
let client = state
.cloud_client_for_profile(input.profile.as_deref())
.await
.map_err(|e| crate::tools::credential_error("cloud", e))?;
let handler = AccountHandler::new(client);
let regions = handler
.get_supported_regions(input.provider)
.await
.tool_context("Failed to get regions")?;
CallToolResult::from_serialize(®ions)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct GetModulesInput {
#[serde(default)]
pub profile: Option<String>,
}
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_safe()
.extractor_handler_typed::<_, _, _, GetModulesInput>(
state,
|State(state): State<Arc<AppState>>, Json(input): Json<GetModulesInput>| async move {
let client = state
.cloud_client_for_profile(input.profile.as_deref())
.await
.map_err(|e| crate::tools::credential_error("cloud", e))?;
let handler = AccountHandler::new(client);
let modules = handler
.get_supported_database_modules()
.await
.tool_context("Failed to get modules")?;
CallToolResult::from_serialize(&modules)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct ListAccountUsersInput {
#[serde(default)]
pub profile: Option<String>,
}
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_safe()
.extractor_handler_typed::<_, _, _, ListAccountUsersInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<ListAccountUsersInput>| async move {
let client = state
.cloud_client_for_profile(input.profile.as_deref())
.await
.map_err(|e| crate::tools::credential_error("cloud", e))?;
let handler = UserHandler::new(client);
let users = handler
.get_all_users()
.await
.tool_context("Failed to list users")?;
CallToolResult::from_serialize(&users)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct GetAccountUserInput {
pub user_id: i32,
#[serde(default)]
pub profile: Option<String>,
}
pub fn get_account_user(state: Arc<AppState>) -> Tool {
ToolBuilder::new("get_account_user")
.description(
"Get detailed information about a specific account user (team member) by ID.",
)
.read_only_safe()
.extractor_handler_typed::<_, _, _, GetAccountUserInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<GetAccountUserInput>| async move {
let client = state
.cloud_client_for_profile(input.profile.as_deref())
.await
.map_err(|e| crate::tools::credential_error("cloud", e))?;
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)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct ListAclUsersInput {
#[serde(default)]
pub profile: Option<String>,
}
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_safe()
.extractor_handler_typed::<_, _, _, ListAclUsersInput>(
state,
|State(state): State<Arc<AppState>>, Json(input): Json<ListAclUsersInput>| async move {
let client = state
.cloud_client_for_profile(input.profile.as_deref())
.await
.map_err(|e| crate::tools::credential_error("cloud", e))?;
let handler = AclHandler::new(client);
let users = handler
.get_all_acl_users()
.await
.tool_context("Failed to list ACL users")?;
CallToolResult::from_serialize(&users)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct GetAclUserInput {
pub user_id: i32,
#[serde(default)]
pub profile: Option<String>,
}
pub fn get_acl_user(state: Arc<AppState>) -> Tool {
ToolBuilder::new("get_acl_user")
.description("Get detailed information about a specific ACL user by ID.")
.read_only_safe()
.extractor_handler_typed::<_, _, _, GetAclUserInput>(
state,
|State(state): State<Arc<AppState>>, Json(input): Json<GetAclUserInput>| async move {
let client = state
.cloud_client_for_profile(input.profile.as_deref())
.await
.map_err(|e| crate::tools::credential_error("cloud", e))?;
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)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct ListAclRolesInput {
#[serde(default)]
pub profile: Option<String>,
}
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_safe()
.extractor_handler_typed::<_, _, _, ListAclRolesInput>(
state,
|State(state): State<Arc<AppState>>, Json(input): Json<ListAclRolesInput>| async move {
let client = state
.cloud_client_for_profile(input.profile.as_deref())
.await
.map_err(|e| crate::tools::credential_error("cloud", e))?;
let handler = AclHandler::new(client);
let roles = handler
.get_roles()
.await
.tool_context("Failed to list ACL roles")?;
CallToolResult::from_serialize(&roles)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct ListRedisRulesInput {
#[serde(default)]
pub profile: Option<String>,
}
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_safe()
.extractor_handler_typed::<_, _, _, ListRedisRulesInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<ListRedisRulesInput>| async move {
let client = state
.cloud_client_for_profile(input.profile.as_deref())
.await
.map_err(|e| crate::tools::credential_error("cloud", e))?;
let handler = AclHandler::new(client);
let rules = handler
.get_all_redis_rules()
.await
.tool_context("Failed to list Redis rules")?;
CallToolResult::from_serialize(&rules)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct CreateAclUserInput {
pub name: String,
pub role: String,
pub password: String,
#[serde(default)]
pub profile: Option<String>,
}
pub fn create_acl_user(state: Arc<AppState>) -> Tool {
ToolBuilder::new("create_acl_user")
.description(
"Create a new ACL user with the assigned database access role. \
Requires write permission.",
)
.non_destructive()
.extractor_handler_typed::<_, _, _, CreateAclUserInput>(
state,
|State(state): State<Arc<AppState>>, Json(input): Json<CreateAclUserInput>| async move {
if !state.is_write_allowed() {
return Err(McpError::tool(
"Write operations not allowed in read-only mode",
));
}
let client = state
.cloud_client_for_profile(input.profile.as_deref())
.await
.map_err(|e| crate::tools::credential_error("cloud", e))?;
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)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct UpdateAclUserInput {
pub user_id: i32,
#[serde(default)]
pub role: Option<String>,
#[serde(default)]
pub password: Option<String>,
#[serde(default)]
pub profile: Option<String>,
}
pub fn update_acl_user(state: Arc<AppState>) -> Tool {
ToolBuilder::new("update_acl_user")
.description(
"Update an ACL user's role or password. \
Requires write permission.",
)
.non_destructive()
.extractor_handler_typed::<_, _, _, UpdateAclUserInput>(
state,
|State(state): State<Arc<AppState>>, Json(input): Json<UpdateAclUserInput>| async move {
if !state.is_write_allowed() {
return Err(McpError::tool(
"Write operations not allowed in read-only mode",
));
}
let client = state
.cloud_client_for_profile(input.profile.as_deref())
.await
.map_err(|e| crate::tools::credential_error("cloud", e))?;
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)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct DeleteAclUserInput {
pub user_id: i32,
#[serde(default)]
pub profile: Option<String>,
}
pub fn delete_acl_user(state: Arc<AppState>) -> Tool {
ToolBuilder::new("delete_acl_user")
.description(
"DANGEROUS: Permanently deletes an ACL user. Active sessions using this user \
will be terminated. Requires write permission.",
)
.destructive()
.extractor_handler_typed::<_, _, _, DeleteAclUserInput>(
state,
|State(state): State<Arc<AppState>>, Json(input): Json<DeleteAclUserInput>| async move {
if !state.is_write_allowed() {
return Err(McpError::tool(
"Write operations not allowed in read-only mode",
));
}
let client = state
.cloud_client_for_profile(input.profile.as_deref())
.await
.map_err(|e| crate::tools::credential_error("cloud", e))?;
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)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct DatabaseSpec {
pub subscription_id: i32,
pub database_id: i32,
#[serde(default)]
pub regions: Option<Vec<String>>,
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct RedisRuleSpec {
pub rule_name: String,
pub databases: Vec<DatabaseSpec>,
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct CreateAclRoleInput {
pub name: String,
pub redis_rules: Vec<RedisRuleSpec>,
#[serde(default)]
pub profile: Option<String>,
}
pub fn create_acl_role(state: Arc<AppState>) -> Tool {
ToolBuilder::new("create_acl_role")
.description(
"Create a new ACL role with assigned Redis rules and database associations. \
Requires write permission.",
)
.non_destructive()
.extractor_handler_typed::<_, _, _, CreateAclRoleInput>(
state,
|State(state): State<Arc<AppState>>, Json(input): Json<CreateAclRoleInput>| async move {
if !state.is_write_allowed() {
return Err(McpError::tool(
"Write operations not allowed in read-only mode",
));
}
let client = state
.cloud_client_for_profile(input.profile.as_deref())
.await
.map_err(|e| crate::tools::credential_error("cloud", e))?;
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)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct UpdateAclRoleInput {
pub role_id: i32,
#[serde(default)]
pub name: Option<String>,
#[serde(default)]
pub redis_rules: Option<Vec<RedisRuleSpec>>,
#[serde(default)]
pub profile: Option<String>,
}
pub fn update_acl_role(state: Arc<AppState>) -> Tool {
ToolBuilder::new("update_acl_role")
.description(
"Update an ACL role's name or Redis rule assignments. \
Requires write permission.",
)
.non_destructive()
.extractor_handler_typed::<_, _, _, UpdateAclRoleInput>(
state,
|State(state): State<Arc<AppState>>, Json(input): Json<UpdateAclRoleInput>| async move {
if !state.is_write_allowed() {
return Err(McpError::tool(
"Write operations not allowed in read-only mode",
));
}
let client = state
.cloud_client_for_profile(input.profile.as_deref())
.await
.map_err(|e| crate::tools::credential_error("cloud", e))?;
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)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct DeleteAclRoleInput {
pub role_id: i32,
#[serde(default)]
pub profile: Option<String>,
}
pub fn delete_acl_role(state: Arc<AppState>) -> Tool {
ToolBuilder::new("delete_acl_role")
.description(
"DANGEROUS: Permanently deletes an ACL role. Users assigned to this role \
will lose their permissions. Requires write permission.",
)
.destructive()
.extractor_handler_typed::<_, _, _, DeleteAclRoleInput>(
state,
|State(state): State<Arc<AppState>>, Json(input): Json<DeleteAclRoleInput>| async move {
if !state.is_write_allowed() {
return Err(McpError::tool(
"Write operations not allowed in read-only mode",
));
}
let client = state
.cloud_client_for_profile(input.profile.as_deref())
.await
.map_err(|e| crate::tools::credential_error("cloud", e))?;
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)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct CreateRedisRuleInput {
pub name: String,
pub redis_rule: String,
#[serde(default)]
pub profile: Option<String>,
}
pub fn create_redis_rule(state: Arc<AppState>) -> Tool {
ToolBuilder::new("create_redis_rule")
.description(
"Create a new Redis ACL rule defining command permissions. \
Requires write permission.",
)
.non_destructive()
.extractor_handler_typed::<_, _, _, CreateRedisRuleInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<CreateRedisRuleInput>| async move {
if !state.is_write_allowed() {
return Err(McpError::tool(
"Write operations not allowed in read-only mode",
));
}
let client = state
.cloud_client_for_profile(input.profile.as_deref())
.await
.map_err(|e| crate::tools::credential_error("cloud", e))?;
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)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct UpdateRedisRuleInput {
pub rule_id: i32,
pub name: String,
pub redis_rule: String,
#[serde(default)]
pub profile: Option<String>,
}
pub fn update_redis_rule(state: Arc<AppState>) -> Tool {
ToolBuilder::new("update_redis_rule")
.description(
"Update a Redis ACL rule's name or pattern. \
Requires write permission.",
)
.non_destructive()
.extractor_handler_typed::<_, _, _, UpdateRedisRuleInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<UpdateRedisRuleInput>| async move {
if !state.is_write_allowed() {
return Err(McpError::tool(
"Write operations not allowed in read-only mode",
));
}
let client = state
.cloud_client_for_profile(input.profile.as_deref())
.await
.map_err(|e| crate::tools::credential_error("cloud", e))?;
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)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct DeleteRedisRuleInput {
pub rule_id: i32,
#[serde(default)]
pub profile: Option<String>,
}
pub fn delete_redis_rule(state: Arc<AppState>) -> Tool {
ToolBuilder::new("delete_redis_rule")
.description(
"DANGEROUS: Permanently deletes a Redis ACL rule. Roles using this rule \
will lose those permissions. Requires write permission.",
)
.destructive()
.extractor_handler_typed::<_, _, _, DeleteRedisRuleInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<DeleteRedisRuleInput>| async move {
if !state.is_write_allowed() {
return Err(McpError::tool(
"Write operations not allowed in read-only mode",
));
}
let client = state
.cloud_client_for_profile(input.profile.as_deref())
.await
.map_err(|e| crate::tools::credential_error("cloud", e))?;
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)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct GenerateCostReportInput {
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>>,
#[serde(default)]
pub profile: Option<String>,
}
pub fn generate_cost_report(state: Arc<AppState>) -> Tool {
ToolBuilder::new("generate_cost_report")
.description(
"Generate a cost report in FOCUS format for the specified date range. \
Returns a task ID to track generation progress. \
Requires write permission.",
)
.non_destructive()
.extractor_handler_typed::<_, _, _, GenerateCostReportInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<GenerateCostReportInput>| async move {
use redis_cloud::{CostReportFormat, SubscriptionType};
if !state.is_write_allowed() {
return Err(McpError::tool(
"Write operations not allowed in read-only mode",
));
}
let client = state
.cloud_client_for_profile(input.profile.as_deref())
.await
.map_err(|e| crate::tools::credential_error("cloud", e))?;
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)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct DownloadCostReportInput {
pub cost_report_id: String,
#[serde(default)]
pub profile: Option<String>,
}
pub fn download_cost_report(state: Arc<AppState>) -> Tool {
ToolBuilder::new("download_cost_report")
.description(
"Download a previously generated cost report by ID. \
Returns the report content (CSV or JSON).",
)
.read_only_safe()
.extractor_handler_typed::<_, _, _, DownloadCostReportInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<DownloadCostReportInput>| async move {
let client = state
.cloud_client_for_profile(input.profile.as_deref())
.await
.map_err(|e| crate::tools::credential_error("cloud", e))?;
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)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct ListPaymentMethodsInput {
#[serde(default)]
pub profile: Option<String>,
}
pub fn list_payment_methods(state: Arc<AppState>) -> Tool {
ToolBuilder::new("list_payment_methods")
.description("List all payment methods for the Redis Cloud account.")
.read_only_safe()
.extractor_handler_typed::<_, _, _, ListPaymentMethodsInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<ListPaymentMethodsInput>| async move {
let client = state
.cloud_client_for_profile(input.profile.as_deref())
.await
.map_err(|e| crate::tools::credential_error("cloud", e))?;
let handler = AccountHandler::new(client);
let methods = handler
.get_account_payment_methods()
.await
.tool_context("Failed to list payment methods")?;
CallToolResult::from_serialize(&methods)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct ListTasksInput {
#[serde(default)]
pub profile: Option<String>,
}
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_safe()
.extractor_handler_typed::<_, _, _, ListTasksInput>(
state,
|State(state): State<Arc<AppState>>, Json(input): Json<ListTasksInput>| async move {
let client = state
.cloud_client_for_profile(input.profile.as_deref())
.await
.map_err(|e| crate::tools::credential_error("cloud", e))?;
let handler = TaskHandler::new(client);
let tasks = handler
.get_all_tasks()
.await
.tool_context("Failed to list tasks")?;
wrap_list("tasks", &tasks)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct GetTaskInput {
pub task_id: String,
#[serde(default)]
pub profile: Option<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_safe()
.extractor_handler_typed::<_, _, _, GetTaskInput>(
state,
|State(state): State<Arc<AppState>>, Json(input): Json<GetTaskInput>| async move {
let client = state
.cloud_client_for_profile(input.profile.as_deref())
.await
.map_err(|e| crate::tools::credential_error("cloud", e))?;
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)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct ListCloudAccountsInput {
#[serde(default)]
pub profile: Option<String>,
}
pub fn list_cloud_accounts(state: Arc<AppState>) -> Tool {
ToolBuilder::new("list_cloud_accounts")
.description(
"List all configured cloud provider accounts (BYOC). Returns cloud accounts \
for AWS, GCP, or Azure that are integrated with Redis Cloud.",
)
.read_only_safe()
.extractor_handler_typed::<_, _, _, ListCloudAccountsInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<ListCloudAccountsInput>| async move {
let client = state
.cloud_client_for_profile(input.profile.as_deref())
.await
.map_err(|e| crate::tools::credential_error("cloud", e))?;
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();
wrap_list("cloud_accounts", &accounts)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct GetCloudAccountInput {
pub cloud_account_id: i32,
#[serde(default)]
pub profile: Option<String>,
}
pub fn get_cloud_account(state: Arc<AppState>) -> Tool {
ToolBuilder::new("get_cloud_account")
.description(
"Get details for a specific cloud provider account (BYOC) by ID, \
including provider type, access credentials, and status.",
)
.read_only_safe()
.extractor_handler_typed::<_, _, _, GetCloudAccountInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<GetCloudAccountInput>| async move {
let client = state
.cloud_client_for_profile(input.profile.as_deref())
.await
.map_err(|e| crate::tools::credential_error("cloud", e))?;
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)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct CreateCloudAccountInput {
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,
#[serde(default)]
pub profile: Option<String>,
}
pub fn create_cloud_account(state: Arc<AppState>) -> Tool {
ToolBuilder::new("create_cloud_account")
.description(
"Create a new cloud provider account (BYOC) for AWS, GCP, or Azure. \
Registers cloud credentials with Redis Cloud for resource provisioning. \
Requires write permission.",
)
.non_destructive()
.extractor_handler_typed::<_, _, _, CreateCloudAccountInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<CreateCloudAccountInput>| async move {
if !state.is_write_allowed() {
return Err(McpError::tool(
"Write operations not allowed in read-only mode",
));
}
let client = state
.cloud_client_for_profile(input.profile.as_deref())
.await
.map_err(|e| crate::tools::credential_error("cloud", e))?;
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)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct UpdateCloudAccountInput {
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>,
#[serde(default)]
pub profile: Option<String>,
}
pub fn update_cloud_account(state: Arc<AppState>) -> Tool {
ToolBuilder::new("update_cloud_account")
.description(
"Update an existing cloud provider account (BYOC) configuration, \
including credentials and console access details. \
Requires write permission.",
)
.non_destructive()
.extractor_handler_typed::<_, _, _, UpdateCloudAccountInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<UpdateCloudAccountInput>| async move {
if !state.is_write_allowed() {
return Err(McpError::tool(
"Write operations not allowed in read-only mode",
));
}
let client = state
.cloud_client_for_profile(input.profile.as_deref())
.await
.map_err(|e| crate::tools::credential_error("cloud", e))?;
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)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct DeleteCloudAccountInput {
pub cloud_account_id: i32,
#[serde(default)]
pub profile: Option<String>,
}
pub fn delete_cloud_account(state: Arc<AppState>) -> Tool {
ToolBuilder::new("delete_cloud_account")
.description(
"DANGEROUS: Permanently deletes a cloud provider account (BYOC). This removes \
the cloud account integration and cannot be undone. Requires write permission.",
)
.destructive()
.extractor_handler_typed::<_, _, _, DeleteCloudAccountInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<DeleteCloudAccountInput>| async move {
if !state.is_write_allowed() {
return Err(McpError::tool(
"Write operations not allowed in read-only mode",
));
}
let client = state
.cloud_client_for_profile(input.profile.as_deref())
.await
.map_err(|e| crate::tools::credential_error("cloud", e))?;
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)
},
)
.build()
}
pub fn router(state: Arc<AppState>) -> McpRouter {
McpRouter::new()
.tool(get_account(state.clone()))
.tool(get_regions(state.clone()))
.tool(get_modules(state.clone()))
.tool(list_account_users(state.clone()))
.tool(get_account_user(state.clone()))
.tool(list_acl_users(state.clone()))
.tool(get_acl_user(state.clone()))
.tool(list_acl_roles(state.clone()))
.tool(list_redis_rules(state.clone()))
.tool(list_payment_methods(state.clone()))
.tool(create_acl_user(state.clone()))
.tool(update_acl_user(state.clone()))
.tool(delete_acl_user(state.clone()))
.tool(create_acl_role(state.clone()))
.tool(update_acl_role(state.clone()))
.tool(delete_acl_role(state.clone()))
.tool(create_redis_rule(state.clone()))
.tool(update_redis_rule(state.clone()))
.tool(delete_redis_rule(state.clone()))
.tool(list_cloud_accounts(state.clone()))
.tool(get_cloud_account(state.clone()))
.tool(create_cloud_account(state.clone()))
.tool(update_cloud_account(state.clone()))
.tool(delete_cloud_account(state.clone()))
.tool(generate_cost_report(state.clone()))
.tool(download_cost_report(state.clone()))
.tool(get_system_logs(state.clone()))
.tool(get_session_logs(state.clone()))
.tool(list_tasks(state.clone()))
.tool(get_task(state.clone()))
}