use std::sync::Arc;
use std::time::Duration;
use redis_enterprise::alerts::AlertHandler;
use redis_enterprise::bdb::{CreateDatabaseRequest, DatabaseHandler};
use redis_enterprise::cluster::ClusterHandler;
use redis_enterprise::debuginfo::DebugInfoHandler;
use redis_enterprise::license::{LicenseHandler, LicenseUpdateRequest};
use redis_enterprise::logs::{LogsHandler, LogsQuery};
use redis_enterprise::modules::ModuleHandler;
use redis_enterprise::nodes::NodeHandler;
use redis_enterprise::redis_acls::RedisAclHandler;
use redis_enterprise::roles::RolesHandler;
use redis_enterprise::shards::ShardHandler;
use redis_enterprise::stats::{StatsHandler, StatsQuery};
use redis_enterprise::users::UserHandler;
use redisctl_core::enterprise::{
backup_database_and_wait, flush_database_and_wait, import_database_and_wait,
};
use schemars::JsonSchema;
use serde::Deserialize;
use serde_json::Value;
use tower_mcp::extract::{Json, State};
use tower_mcp::{CallToolResult, Error as McpError, McpRouter, Tool, ToolBuilder, ToolError};
use crate::state::AppState;
use crate::tools::wrap_list;
#[derive(Debug, Deserialize, JsonSchema)]
pub struct GetClusterInput {
#[serde(default)]
pub profile: Option<String>,
}
pub fn get_cluster(state: Arc<AppState>) -> Tool {
ToolBuilder::new("get_cluster")
.description(
"Get Redis Enterprise cluster information including name, version, and configuration",
)
.read_only()
.idempotent()
.extractor_handler_typed::<_, _, _, GetClusterInput>(
state,
|State(state): State<Arc<AppState>>, Json(input): Json<GetClusterInput>| async move {
let client = state
.enterprise_client_for_profile(input.profile.as_deref())
.await
.map_err(|e| {
ToolError::new(format!("Failed to get Enterprise client: {}", e))
})?;
let handler = ClusterHandler::new(client);
let cluster = handler
.info()
.await
.map_err(|e| ToolError::new(format!("Failed to get cluster info: {}", e)))?;
CallToolResult::from_serialize(&cluster)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct GetLicenseInput {
#[serde(default)]
pub profile: Option<String>,
}
pub fn get_license(state: Arc<AppState>) -> Tool {
ToolBuilder::new("get_license")
.description(
"Get Redis Enterprise cluster license information including type, expiration date, \
cluster name, owner, and enabled features",
)
.read_only()
.idempotent()
.extractor_handler_typed::<_, _, _, GetLicenseInput>(
state,
|State(state): State<Arc<AppState>>, Json(input): Json<GetLicenseInput>| async move {
let client = state
.enterprise_client_for_profile(input.profile.as_deref())
.await
.map_err(|e| {
ToolError::new(format!("Failed to get Enterprise client: {}", e))
})?;
let handler = LicenseHandler::new(client);
let license = handler
.get()
.await
.map_err(|e| ToolError::new(format!("Failed to get license: {}", e)))?;
CallToolResult::from_serialize(&license)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct GetLicenseUsageInput {
#[serde(default)]
pub profile: Option<String>,
}
pub fn get_license_usage(state: Arc<AppState>) -> Tool {
ToolBuilder::new("get_license_usage")
.description(
"Get Redis Enterprise cluster license utilization statistics including shards, \
nodes, and RAM usage against license limits",
)
.read_only()
.idempotent()
.extractor_handler_typed::<_, _, _, GetLicenseUsageInput>(
state,
|State(state): State<Arc<AppState>>, Json(input): Json<GetLicenseUsageInput>| async move {
let client = state
.enterprise_client_for_profile(input.profile.as_deref())
.await
.map_err(|e| ToolError::new(format!("Failed to get Enterprise client: {}", e)))?;
let handler = LicenseHandler::new(client);
let usage = handler
.usage()
.await
.map_err(|e| ToolError::new(format!("Failed to get license usage: {}", e)))?;
CallToolResult::from_serialize(&usage)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct ListLogsInput {
#[serde(default)]
pub profile: Option<String>,
#[serde(default)]
pub start_time: Option<String>,
#[serde(default)]
pub end_time: Option<String>,
#[serde(default)]
pub order: Option<String>,
#[serde(default)]
pub limit: Option<u32>,
#[serde(default)]
pub offset: Option<u32>,
}
pub fn list_logs(state: Arc<AppState>) -> Tool {
ToolBuilder::new("list_logs")
.description(
"List cluster event logs from Redis Enterprise. Logs include events like database \
changes, node status updates, configuration modifications, and alerts. Supports \
filtering by time range and pagination.",
)
.read_only()
.idempotent()
.extractor_handler_typed::<_, _, _, ListLogsInput>(
state,
|State(state): State<Arc<AppState>>, Json(input): Json<ListLogsInput>| async move {
let client = state
.enterprise_client_for_profile(input.profile.as_deref())
.await
.map_err(|e| {
ToolError::new(format!("Failed to get Enterprise client: {}", e))
})?;
let query = if input.start_time.is_some()
|| input.end_time.is_some()
|| input.order.is_some()
|| input.limit.is_some()
|| input.offset.is_some()
{
Some(LogsQuery {
stime: input.start_time,
etime: input.end_time,
order: input.order,
limit: input.limit,
offset: input.offset,
})
} else {
None
};
let handler = LogsHandler::new(client);
let logs = handler
.list(query)
.await
.map_err(|e| ToolError::new(format!("Failed to list logs: {}", e)))?;
wrap_list("logs", &logs)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct ListDatabasesInput {
#[serde(default)]
pub profile: Option<String>,
#[serde(default)]
pub name_filter: Option<String>,
#[serde(default)]
pub status_filter: Option<String>,
}
pub fn list_databases(state: Arc<AppState>) -> Tool {
ToolBuilder::new("list_enterprise_databases")
.description(
"List all databases on the Redis Enterprise cluster. Supports filtering by name \
(case-insensitive substring match) and status.",
)
.read_only()
.idempotent()
.extractor_handler_typed::<_, _, _, ListDatabasesInput>(
state,
|State(state): State<Arc<AppState>>, Json(input): Json<ListDatabasesInput>| async move {
let client = state
.enterprise_client_for_profile(input.profile.as_deref())
.await
.map_err(|e| {
ToolError::new(format!("Failed to get Enterprise client: {}", e))
})?;
let handler = DatabaseHandler::new(client);
let databases = handler
.list()
.await
.map_err(|e| ToolError::new(format!("Failed to list databases: {}", e)))?;
let filtered: Vec<_> = databases
.into_iter()
.filter(|db| {
if let Some(filter) = &input.name_filter {
db.name.to_lowercase().contains(&filter.to_lowercase())
} else {
true
}
})
.filter(|db| {
if let Some(filter) = &input.status_filter {
db.status
.as_ref()
.map(|s| s.to_lowercase() == filter.to_lowercase())
.unwrap_or(false)
} else {
true
}
})
.collect();
wrap_list("databases", &filtered)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct GetDatabaseInput {
#[serde(default)]
pub profile: Option<String>,
pub uid: u32,
}
pub fn get_database(state: Arc<AppState>) -> Tool {
ToolBuilder::new("get_enterprise_database")
.description("Get detailed information about a specific Redis Enterprise database")
.read_only()
.idempotent()
.extractor_handler_typed::<_, _, _, GetDatabaseInput>(
state,
|State(state): State<Arc<AppState>>, Json(input): Json<GetDatabaseInput>| async move {
let client = state
.enterprise_client_for_profile(input.profile.as_deref())
.await
.map_err(|e| {
ToolError::new(format!("Failed to get Enterprise client: {}", e))
})?;
let handler = DatabaseHandler::new(client);
let database = handler
.get(input.uid)
.await
.map_err(|e| ToolError::new(format!("Failed to get database: {}", e)))?;
CallToolResult::from_serialize(&database)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct ListNodesInput {
#[serde(default)]
pub profile: Option<String>,
}
pub fn list_nodes(state: Arc<AppState>) -> Tool {
ToolBuilder::new("list_nodes")
.description("List all nodes in the Redis Enterprise cluster")
.read_only()
.idempotent()
.extractor_handler_typed::<_, _, _, ListNodesInput>(
state,
|State(state): State<Arc<AppState>>, Json(input): Json<ListNodesInput>| async move {
let client = state
.enterprise_client_for_profile(input.profile.as_deref())
.await
.map_err(|e| {
ToolError::new(format!("Failed to get Enterprise client: {}", e))
})?;
let handler = NodeHandler::new(client);
let nodes = handler
.list()
.await
.map_err(|e| ToolError::new(format!("Failed to list nodes: {}", e)))?;
wrap_list("nodes", &nodes)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct GetNodeInput {
#[serde(default)]
pub profile: Option<String>,
pub uid: u32,
}
pub fn get_node(state: Arc<AppState>) -> Tool {
ToolBuilder::new("get_node")
.description(
"Get detailed information about a specific node in the Redis Enterprise cluster",
)
.read_only()
.idempotent()
.extractor_handler_typed::<_, _, _, GetNodeInput>(
state,
|State(state): State<Arc<AppState>>, Json(input): Json<GetNodeInput>| async move {
let client = state
.enterprise_client_for_profile(input.profile.as_deref())
.await
.map_err(|e| {
ToolError::new(format!("Failed to get Enterprise client: {}", e))
})?;
let handler = NodeHandler::new(client);
let node = handler
.get(input.uid)
.await
.map_err(|e| ToolError::new(format!("Failed to get node: {}", e)))?;
CallToolResult::from_serialize(&node)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct ListUsersInput {
#[serde(default)]
pub profile: Option<String>,
}
pub fn list_users(state: Arc<AppState>) -> Tool {
ToolBuilder::new("list_enterprise_users")
.description("List all users in the Redis Enterprise cluster")
.read_only()
.idempotent()
.extractor_handler_typed::<_, _, _, ListUsersInput>(
state,
|State(state): State<Arc<AppState>>, Json(input): Json<ListUsersInput>| async move {
let client = state
.enterprise_client_for_profile(input.profile.as_deref())
.await
.map_err(|e| {
ToolError::new(format!("Failed to get Enterprise client: {}", e))
})?;
let handler = UserHandler::new(client);
let users = handler
.list()
.await
.map_err(|e| ToolError::new(format!("Failed to list users: {}", e)))?;
wrap_list("users", &users)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct GetUserInput {
#[serde(default)]
pub profile: Option<String>,
pub uid: u32,
}
pub fn get_user(state: Arc<AppState>) -> Tool {
ToolBuilder::new("get_enterprise_user")
.description(
"Get detailed information about a specific user in the Redis Enterprise cluster",
)
.read_only()
.idempotent()
.extractor_handler_typed::<_, _, _, GetUserInput>(
state,
|State(state): State<Arc<AppState>>, Json(input): Json<GetUserInput>| async move {
let client = state
.enterprise_client_for_profile(input.profile.as_deref())
.await
.map_err(|e| {
ToolError::new(format!("Failed to get Enterprise client: {}", e))
})?;
let handler = UserHandler::new(client);
let user = handler
.get(input.uid)
.await
.map_err(|e| ToolError::new(format!("Failed to get user: {}", e)))?;
CallToolResult::from_serialize(&user)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct ListAlertsInput {
#[serde(default)]
pub profile: Option<String>,
}
pub fn list_alerts(state: Arc<AppState>) -> Tool {
ToolBuilder::new("list_alerts")
.description("List all active alerts in the Redis Enterprise cluster")
.read_only()
.idempotent()
.extractor_handler_typed::<_, _, _, ListAlertsInput>(
state,
|State(state): State<Arc<AppState>>, Json(input): Json<ListAlertsInput>| async move {
let client = state
.enterprise_client_for_profile(input.profile.as_deref())
.await
.map_err(|e| {
ToolError::new(format!("Failed to get Enterprise client: {}", e))
})?;
let handler = AlertHandler::new(client);
let alerts = handler
.list()
.await
.map_err(|e| ToolError::new(format!("Failed to list alerts: {}", e)))?;
wrap_list("alerts", &alerts)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct ListDatabaseAlertsInput {
#[serde(default)]
pub profile: Option<String>,
pub uid: u32,
}
pub fn list_database_alerts(state: Arc<AppState>) -> Tool {
ToolBuilder::new("list_database_alerts")
.description("List all alerts for a specific database in the Redis Enterprise cluster")
.read_only()
.idempotent()
.extractor_handler_typed::<_, _, _, ListDatabaseAlertsInput>(
state,
|State(state): State<Arc<AppState>>, Json(input): Json<ListDatabaseAlertsInput>| async move {
let client = state
.enterprise_client_for_profile(input.profile.as_deref())
.await
.map_err(|e| ToolError::new(format!("Failed to get Enterprise client: {}", e)))?;
let handler = AlertHandler::new(client);
let alerts = handler
.list_by_database(input.uid)
.await
.map_err(|e| ToolError::new(format!("Failed to list database alerts: {}", e)))?;
wrap_list("alerts", &alerts)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct GetClusterStatsInput {
#[serde(default)]
pub profile: Option<String>,
#[serde(default)]
pub interval: Option<String>,
#[serde(default)]
pub start_time: Option<String>,
#[serde(default)]
pub end_time: Option<String>,
}
pub fn get_cluster_stats(state: Arc<AppState>) -> Tool {
ToolBuilder::new("get_cluster_stats")
.description(
"Get statistics for the Redis Enterprise cluster. By default returns the latest \
stats. Optionally specify interval and time range for historical data.",
)
.read_only()
.idempotent()
.extractor_handler_typed::<_, _, _, GetClusterStatsInput>(
state,
|State(state): State<Arc<AppState>>, Json(input): Json<GetClusterStatsInput>| async move {
let client = state
.enterprise_client_for_profile(input.profile.as_deref())
.await
.map_err(|e| ToolError::new(format!("Failed to get Enterprise client: {}", e)))?;
let handler = StatsHandler::new(client);
if input.interval.is_some() || input.start_time.is_some() || input.end_time.is_some() {
let query = StatsQuery {
interval: input.interval,
stime: input.start_time,
etime: input.end_time,
metrics: None,
};
let stats = handler
.cluster(Some(query))
.await
.map_err(|e| ToolError::new(format!("Failed to get cluster stats: {}", e)))?;
CallToolResult::from_serialize(&stats)
} else {
let stats = handler
.cluster_last()
.await
.map_err(|e| ToolError::new(format!("Failed to get cluster stats: {}", e)))?;
CallToolResult::from_serialize(&stats)
}
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct GetDatabaseStatsInput {
#[serde(default)]
pub profile: Option<String>,
pub uid: u32,
#[serde(default)]
pub interval: Option<String>,
#[serde(default)]
pub start_time: Option<String>,
#[serde(default)]
pub end_time: Option<String>,
}
pub fn get_database_stats(state: Arc<AppState>) -> Tool {
ToolBuilder::new("get_database_stats")
.description(
"Get statistics for a specific database. By default returns the latest stats. \
Optionally specify interval and time range for historical data.",
)
.read_only()
.idempotent()
.extractor_handler_typed::<_, _, _, GetDatabaseStatsInput>(
state,
|State(state): State<Arc<AppState>>, Json(input): Json<GetDatabaseStatsInput>| async move {
let client = state
.enterprise_client_for_profile(input.profile.as_deref())
.await
.map_err(|e| ToolError::new(format!("Failed to get Enterprise client: {}", e)))?;
let handler = StatsHandler::new(client);
if input.interval.is_some() || input.start_time.is_some() || input.end_time.is_some() {
let query = StatsQuery {
interval: input.interval,
stime: input.start_time,
etime: input.end_time,
metrics: None,
};
let stats = handler
.database(input.uid, Some(query))
.await
.map_err(|e| ToolError::new(format!("Failed to get database stats: {}", e)))?;
CallToolResult::from_serialize(&stats)
} else {
let stats = handler
.database_last(input.uid)
.await
.map_err(|e| ToolError::new(format!("Failed to get database stats: {}", e)))?;
CallToolResult::from_serialize(&stats)
}
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct GetNodeStatsInput {
#[serde(default)]
pub profile: Option<String>,
pub uid: u32,
#[serde(default)]
pub interval: Option<String>,
#[serde(default)]
pub start_time: Option<String>,
#[serde(default)]
pub end_time: Option<String>,
}
pub fn get_node_stats(state: Arc<AppState>) -> Tool {
ToolBuilder::new("get_node_stats")
.description(
"Get statistics for a specific node. By default returns the latest stats. \
Optionally specify interval and time range for historical data.",
)
.read_only()
.idempotent()
.extractor_handler_typed::<_, _, _, GetNodeStatsInput>(
state,
|State(state): State<Arc<AppState>>, Json(input): Json<GetNodeStatsInput>| async move {
let client = state
.enterprise_client_for_profile(input.profile.as_deref())
.await
.map_err(|e| {
ToolError::new(format!("Failed to get Enterprise client: {}", e))
})?;
let handler = StatsHandler::new(client);
if input.interval.is_some()
|| input.start_time.is_some()
|| input.end_time.is_some()
{
let query = StatsQuery {
interval: input.interval,
stime: input.start_time,
etime: input.end_time,
metrics: None,
};
let stats = handler
.node(input.uid, Some(query))
.await
.map_err(|e| ToolError::new(format!("Failed to get node stats: {}", e)))?;
CallToolResult::from_serialize(&stats)
} else {
let stats = handler
.node_last(input.uid)
.await
.map_err(|e| ToolError::new(format!("Failed to get node stats: {}", e)))?;
CallToolResult::from_serialize(&stats)
}
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct GetAllNodesStatsInput {
#[serde(default)]
pub profile: Option<String>,
}
pub fn get_all_nodes_stats(state: Arc<AppState>) -> Tool {
ToolBuilder::new("get_all_nodes_stats")
.description(
"Get current statistics for all nodes in the Redis Enterprise cluster in a single \
call. Returns aggregated stats per node including CPU, memory, and network metrics.",
)
.read_only()
.idempotent()
.extractor_handler_typed::<_, _, _, GetAllNodesStatsInput>(
state,
|State(state): State<Arc<AppState>>, Json(input): Json<GetAllNodesStatsInput>| async move {
let client = state
.enterprise_client_for_profile(input.profile.as_deref())
.await
.map_err(|e| ToolError::new(format!("Failed to get Enterprise client: {}", e)))?;
let handler = StatsHandler::new(client);
let stats = handler
.nodes_last()
.await
.map_err(|e| ToolError::new(format!("Failed to get all nodes stats: {}", e)))?;
CallToolResult::from_serialize(&stats)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct GetAllDatabasesStatsInput {
#[serde(default)]
pub profile: Option<String>,
}
pub fn get_all_databases_stats(state: Arc<AppState>) -> Tool {
ToolBuilder::new("get_all_databases_stats")
.description(
"Get current statistics for all databases in the Redis Enterprise cluster in a \
single call. Returns aggregated stats per database including latency, throughput, \
and memory usage.",
)
.read_only()
.idempotent()
.extractor_handler_typed::<_, _, _, GetAllDatabasesStatsInput>(
state,
|State(state): State<Arc<AppState>>, Json(input): Json<GetAllDatabasesStatsInput>| async move {
let client = state
.enterprise_client_for_profile(input.profile.as_deref())
.await
.map_err(|e| ToolError::new(format!("Failed to get Enterprise client: {}", e)))?;
let handler = StatsHandler::new(client);
let stats = handler.databases_last().await.map_err(|e| {
ToolError::new(format!("Failed to get all databases stats: {}", e))
})?;
CallToolResult::from_serialize(&stats)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct GetShardStatsInput {
#[serde(default)]
pub profile: Option<String>,
pub uid: u32,
}
pub fn get_shard_stats(state: Arc<AppState>) -> Tool {
ToolBuilder::new("get_shard_stats")
.description("Get current statistics for a specific shard in the Redis Enterprise cluster")
.read_only()
.idempotent()
.extractor_handler_typed::<_, _, _, GetShardStatsInput>(
state,
|State(state): State<Arc<AppState>>, Json(input): Json<GetShardStatsInput>| async move {
let client = state
.enterprise_client_for_profile(input.profile.as_deref())
.await
.map_err(|e| {
ToolError::new(format!("Failed to get Enterprise client: {}", e))
})?;
let handler = StatsHandler::new(client);
let stats = handler
.shard(input.uid, None)
.await
.map_err(|e| ToolError::new(format!("Failed to get shard stats: {}", e)))?;
CallToolResult::from_serialize(&stats)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct GetAllShardsStatsInput {
#[serde(default)]
pub profile: Option<String>,
}
pub fn get_all_shards_stats(state: Arc<AppState>) -> Tool {
ToolBuilder::new("get_all_shards_stats")
.description(
"Get current statistics for all shards in the Redis Enterprise cluster in a single \
call. Returns aggregated stats per shard.",
)
.read_only()
.idempotent()
.extractor_handler_typed::<_, _, _, GetAllShardsStatsInput>(
state,
|State(state): State<Arc<AppState>>, Json(input): Json<GetAllShardsStatsInput>| async move {
let client = state
.enterprise_client_for_profile(input.profile.as_deref())
.await
.map_err(|e| ToolError::new(format!("Failed to get Enterprise client: {}", e)))?;
let handler = StatsHandler::new(client);
let stats = handler
.shards(None)
.await
.map_err(|e| ToolError::new(format!("Failed to get all shards stats: {}", e)))?;
CallToolResult::from_serialize(&stats)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct ListShardsInput {
#[serde(default)]
pub profile: Option<String>,
#[serde(default)]
pub database_uid: Option<u32>,
}
pub fn list_shards(state: Arc<AppState>) -> Tool {
ToolBuilder::new("list_shards")
.description(
"List all shards in the Redis Enterprise cluster. Optionally filter by database UID.",
)
.read_only()
.idempotent()
.extractor_handler_typed::<_, _, _, ListShardsInput>(
state,
|State(state): State<Arc<AppState>>, Json(input): Json<ListShardsInput>| async move {
let client = state
.enterprise_client_for_profile(input.profile.as_deref())
.await
.map_err(|e| {
ToolError::new(format!("Failed to get Enterprise client: {}", e))
})?;
let handler = ShardHandler::new(client);
let shards = if let Some(db_uid) = input.database_uid {
handler
.list_by_database(db_uid)
.await
.map_err(|e| ToolError::new(format!("Failed to list shards: {}", e)))?
} else {
handler
.list()
.await
.map_err(|e| ToolError::new(format!("Failed to list shards: {}", e)))?
};
wrap_list("shards", &shards)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct GetShardInput {
#[serde(default)]
pub profile: Option<String>,
pub uid: String,
}
pub fn get_shard(state: Arc<AppState>) -> Tool {
ToolBuilder::new("get_shard")
.description(
"Get detailed information about a specific shard in the Redis Enterprise cluster \
including role (master/replica), status, and assigned node.",
)
.read_only()
.idempotent()
.extractor_handler_typed::<_, _, _, GetShardInput>(
state,
|State(state): State<Arc<AppState>>, Json(input): Json<GetShardInput>| async move {
let client = state
.enterprise_client_for_profile(input.profile.as_deref())
.await
.map_err(|e| {
ToolError::new(format!("Failed to get Enterprise client: {}", e))
})?;
let handler = ShardHandler::new(client);
let shard = handler
.get(&input.uid)
.await
.map_err(|e| ToolError::new(format!("Failed to get shard: {}", e)))?;
CallToolResult::from_serialize(&shard)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct GetDatabaseEndpointsInput {
#[serde(default)]
pub profile: Option<String>,
pub uid: u32,
}
pub fn get_database_endpoints(state: Arc<AppState>) -> Tool {
ToolBuilder::new("get_database_endpoints")
.description(
"Get connection endpoints for a specific database in the Redis Enterprise cluster",
)
.read_only()
.idempotent()
.extractor_handler_typed::<_, _, _, GetDatabaseEndpointsInput>(
state,
|State(state): State<Arc<AppState>>, Json(input): Json<GetDatabaseEndpointsInput>| async move {
let client = state
.enterprise_client_for_profile(input.profile.as_deref())
.await
.map_err(|e| ToolError::new(format!("Failed to get Enterprise client: {}", e)))?;
let handler = DatabaseHandler::new(client);
let endpoints = handler
.endpoints(input.uid)
.await
.map_err(|e| ToolError::new(format!("Failed to get endpoints: {}", e)))?;
wrap_list("endpoints", &endpoints)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct ListDebugInfoTasksInput {
#[serde(default)]
pub profile: Option<String>,
}
pub fn list_debug_info_tasks(state: Arc<AppState>) -> Tool {
ToolBuilder::new("list_debug_info_tasks")
.description(
"List all debug info collection tasks in the Redis Enterprise cluster. Returns task \
IDs, statuses (queued, running, completed, failed), and download URLs for completed \
collections.",
)
.read_only()
.idempotent()
.extractor_handler_typed::<_, _, _, ListDebugInfoTasksInput>(
state,
|State(state): State<Arc<AppState>>, Json(input): Json<ListDebugInfoTasksInput>| async move {
let client = state
.enterprise_client_for_profile(input.profile.as_deref())
.await
.map_err(|e| ToolError::new(format!("Failed to get Enterprise client: {}", e)))?;
let handler = DebugInfoHandler::new(client);
let tasks = handler
.list()
.await
.map_err(|e| ToolError::new(format!("Failed to list debug info tasks: {}", e)))?;
wrap_list("tasks", &tasks)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct GetDebugInfoStatusInput {
#[serde(default)]
pub profile: Option<String>,
pub task_id: String,
}
pub fn get_debug_info_status(state: Arc<AppState>) -> Tool {
ToolBuilder::new("get_debug_info_status")
.description(
"Get the status of a debug info collection task. Returns status (queued, running, \
completed, failed), progress percentage, download URL (when completed), and error \
message (if failed).",
)
.read_only()
.idempotent()
.extractor_handler_typed::<_, _, _, GetDebugInfoStatusInput>(
state,
|State(state): State<Arc<AppState>>, Json(input): Json<GetDebugInfoStatusInput>| async move {
let client = state
.enterprise_client_for_profile(input.profile.as_deref())
.await
.map_err(|e| ToolError::new(format!("Failed to get Enterprise client: {}", e)))?;
let handler = DebugInfoHandler::new(client);
let status = handler
.status(&input.task_id)
.await
.map_err(|e| ToolError::new(format!("Failed to get debug info status: {}", e)))?;
CallToolResult::from_serialize(&status)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct ListModulesInput {
#[serde(default)]
pub profile: Option<String>,
}
pub fn list_modules(state: Arc<AppState>) -> Tool {
ToolBuilder::new("list_modules")
.description(
"List all Redis modules installed on the Redis Enterprise cluster. Returns module \
names, versions, descriptions, and capabilities (e.g., RedisJSON, RediSearch, \
RedisTimeSeries).",
)
.read_only()
.idempotent()
.extractor_handler_typed::<_, _, _, ListModulesInput>(
state,
|State(state): State<Arc<AppState>>, Json(input): Json<ListModulesInput>| async move {
let client = state
.enterprise_client_for_profile(input.profile.as_deref())
.await
.map_err(|e| {
ToolError::new(format!("Failed to get Enterprise client: {}", e))
})?;
let handler = ModuleHandler::new(client);
let modules = handler
.list()
.await
.map_err(|e| ToolError::new(format!("Failed to list modules: {}", e)))?;
wrap_list("modules", &modules)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct GetModuleInput {
#[serde(default)]
pub profile: Option<String>,
pub uid: String,
}
pub fn get_module(state: Arc<AppState>) -> Tool {
ToolBuilder::new("get_module")
.description(
"Get detailed information about a specific Redis module including version, \
description, author, license, capabilities, and platform compatibility.",
)
.read_only()
.idempotent()
.extractor_handler_typed::<_, _, _, GetModuleInput>(
state,
|State(state): State<Arc<AppState>>, Json(input): Json<GetModuleInput>| async move {
let client = state
.enterprise_client_for_profile(input.profile.as_deref())
.await
.map_err(|e| {
ToolError::new(format!("Failed to get Enterprise client: {}", e))
})?;
let handler = ModuleHandler::new(client);
let module = handler
.get(&input.uid)
.await
.map_err(|e| ToolError::new(format!("Failed to get module: {}", e)))?;
CallToolResult::from_serialize(&module)
},
)
.build()
}
fn default_enterprise_timeout() -> u64 {
600
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct BackupDatabaseInput {
#[serde(default)]
pub profile: Option<String>,
pub bdb_uid: u32,
#[serde(default = "default_enterprise_timeout")]
pub timeout_seconds: u64,
}
pub fn backup_enterprise_database(state: Arc<AppState>) -> Tool {
ToolBuilder::new("backup_enterprise_database")
.description(
"Trigger a backup of a Redis Enterprise database and wait for completion. \
Requires write permission.",
)
.extractor_handler_typed::<_, _, _, BackupDatabaseInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<BackupDatabaseInput>| async move {
if !state.is_write_allowed() {
return Err(McpError::tool(
"Write operations not allowed in read-only mode",
));
}
let client = state
.enterprise_client_for_profile(input.profile.as_deref())
.await
.map_err(|e| ToolError::new(format!("Failed to get Enterprise client: {}", e)))?;
backup_database_and_wait(
&client,
input.bdb_uid,
Duration::from_secs(input.timeout_seconds),
None,
)
.await
.map_err(|e| ToolError::new(format!("Failed to backup database: {}", e)))?;
CallToolResult::from_serialize(&serde_json::json!({
"message": "Backup completed successfully",
"bdb_uid": input.bdb_uid
}))
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct ImportDatabaseInput {
#[serde(default)]
pub profile: Option<String>,
pub bdb_uid: u32,
pub import_location: String,
#[serde(default)]
pub flush: bool,
#[serde(default = "default_enterprise_timeout")]
pub timeout_seconds: u64,
}
pub fn import_enterprise_database(state: Arc<AppState>) -> Tool {
ToolBuilder::new("import_enterprise_database")
.description(
"Import data into a Redis Enterprise database from an external source and wait for completion. \
WARNING: If flush is true, existing data will be deleted before import. \
Requires write permission.",
)
.extractor_handler_typed::<_, _, _, ImportDatabaseInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<ImportDatabaseInput>| async move {
if !state.is_write_allowed() {
return Err(McpError::tool(
"Write operations not allowed in read-only mode",
));
}
let client = state
.enterprise_client_for_profile(input.profile.as_deref())
.await
.map_err(|e| ToolError::new(format!("Failed to get Enterprise client: {}", e)))?;
import_database_and_wait(
&client,
input.bdb_uid,
&input.import_location,
input.flush,
Duration::from_secs(input.timeout_seconds),
None,
)
.await
.map_err(|e| ToolError::new(format!("Failed to import database: {}", e)))?;
CallToolResult::from_serialize(&serde_json::json!({
"message": "Import completed successfully",
"bdb_uid": input.bdb_uid,
"import_location": input.import_location
}))
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct CreateEnterpriseDatabaseInput {
#[serde(default)]
pub profile: Option<String>,
pub name: String,
pub memory_size: Option<u64>,
pub port: Option<u16>,
#[serde(default)]
pub replication: Option<bool>,
pub persistence: Option<String>,
pub eviction_policy: Option<String>,
#[serde(default)]
pub sharding: Option<bool>,
pub shards_count: Option<u32>,
}
pub fn create_enterprise_database(state: Arc<AppState>) -> Tool {
ToolBuilder::new("create_enterprise_database")
.description(
"Create a new database in the Redis Enterprise cluster. \
Returns the created database details. Requires write permission.",
)
.extractor_handler_typed::<_, _, _, CreateEnterpriseDatabaseInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<CreateEnterpriseDatabaseInput>| async move {
if !state.is_write_allowed() {
return Err(McpError::tool(
"Write operations not allowed in read-only mode",
));
}
let client = state
.enterprise_client_for_profile(input.profile.as_deref())
.await
.map_err(|e| {
ToolError::new(format!("Failed to get Enterprise client: {}", e))
})?;
let request = CreateDatabaseRequest {
name: input.name.clone(),
memory_size: input.memory_size,
port: input.port,
replication: input.replication,
persistence: input.persistence.clone(),
eviction_policy: input.eviction_policy.clone(),
sharding: input.sharding,
shards_count: input.shards_count,
shard_count: None,
proxy_policy: None,
rack_aware: None,
module_list: None,
crdt: None,
authentication_redis_pass: None,
};
let handler = DatabaseHandler::new(client);
let database = handler
.create(request)
.await
.map_err(|e| ToolError::new(format!("Failed to create database: {}", e)))?;
CallToolResult::from_serialize(&database)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct UpdateEnterpriseDatabaseInput {
#[serde(default)]
pub profile: Option<String>,
pub uid: u32,
pub updates: Value,
}
pub fn update_enterprise_database(state: Arc<AppState>) -> Tool {
ToolBuilder::new("update_enterprise_database")
.description(
"Update configuration of an existing Redis Enterprise database. \
Pass a JSON object with the fields to update. Requires write permission.",
)
.extractor_handler_typed::<_, _, _, UpdateEnterpriseDatabaseInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<UpdateEnterpriseDatabaseInput>| async move {
if !state.is_write_allowed() {
return Err(McpError::tool(
"Write operations not allowed in read-only mode",
));
}
let client = state
.enterprise_client_for_profile(input.profile.as_deref())
.await
.map_err(|e| {
ToolError::new(format!("Failed to get Enterprise client: {}", e))
})?;
let handler = DatabaseHandler::new(client);
let database = handler
.update(input.uid, input.updates)
.await
.map_err(|e| ToolError::new(format!("Failed to update database: {}", e)))?;
CallToolResult::from_serialize(&database)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct DeleteEnterpriseDatabaseInput {
#[serde(default)]
pub profile: Option<String>,
pub uid: u32,
}
pub fn delete_enterprise_database(state: Arc<AppState>) -> Tool {
ToolBuilder::new("delete_enterprise_database")
.description(
"Delete a database from the Redis Enterprise cluster. \
WARNING: This permanently deletes the database and all its data! \
Requires write permission.",
)
.extractor_handler_typed::<_, _, _, DeleteEnterpriseDatabaseInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<DeleteEnterpriseDatabaseInput>| async move {
if !state.is_write_allowed() {
return Err(McpError::tool(
"Write operations not allowed in read-only mode",
));
}
let client = state
.enterprise_client_for_profile(input.profile.as_deref())
.await
.map_err(|e| {
ToolError::new(format!("Failed to get Enterprise client: {}", e))
})?;
let handler = DatabaseHandler::new(client);
handler
.delete(input.uid)
.await
.map_err(|e| ToolError::new(format!("Failed to delete database: {}", e)))?;
CallToolResult::from_serialize(&serde_json::json!({
"message": "Database deleted successfully",
"uid": input.uid
}))
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct FlushEnterpriseDatabaseInput {
#[serde(default)]
pub profile: Option<String>,
pub bdb_uid: u32,
#[serde(default = "default_enterprise_timeout")]
pub timeout_seconds: u64,
}
pub fn flush_enterprise_database(state: Arc<AppState>) -> Tool {
ToolBuilder::new("flush_enterprise_database")
.description(
"Flush all data from a Redis Enterprise database and wait for completion. \
WARNING: This permanently deletes ALL data in the database! \
Requires write permission.",
)
.extractor_handler_typed::<_, _, _, FlushEnterpriseDatabaseInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<FlushEnterpriseDatabaseInput>| async move {
if !state.is_write_allowed() {
return Err(McpError::tool(
"Write operations not allowed in read-only mode",
));
}
let client = state
.enterprise_client_for_profile(input.profile.as_deref())
.await
.map_err(|e| {
ToolError::new(format!("Failed to get Enterprise client: {}", e))
})?;
flush_database_and_wait(
&client,
input.bdb_uid,
Duration::from_secs(input.timeout_seconds),
None,
)
.await
.map_err(|e| ToolError::new(format!("Failed to flush database: {}", e)))?;
CallToolResult::from_serialize(&serde_json::json!({
"message": "Database flushed successfully",
"bdb_uid": input.bdb_uid
}))
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct ListRolesInput {
#[serde(default)]
pub profile: Option<String>,
}
pub fn list_roles(state: Arc<AppState>) -> Tool {
ToolBuilder::new("list_enterprise_roles")
.description(
"List all roles in the Redis Enterprise cluster. Returns role names, \
permissions (management, data_access), and database-specific role assignments.",
)
.read_only()
.idempotent()
.extractor_handler_typed::<_, _, _, ListRolesInput>(
state,
|State(state): State<Arc<AppState>>, Json(input): Json<ListRolesInput>| async move {
let client = state
.enterprise_client_for_profile(input.profile.as_deref())
.await
.map_err(|e| {
ToolError::new(format!("Failed to get Enterprise client: {}", e))
})?;
let handler = RolesHandler::new(client);
let roles = handler
.list()
.await
.map_err(|e| ToolError::new(format!("Failed to list roles: {}", e)))?;
wrap_list("roles", &roles)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct GetRoleInput {
#[serde(default)]
pub profile: Option<String>,
pub uid: u32,
}
pub fn get_role(state: Arc<AppState>) -> Tool {
ToolBuilder::new("get_enterprise_role")
.description(
"Get detailed information about a specific role including permissions \
and database role assignments.",
)
.read_only()
.idempotent()
.extractor_handler_typed::<_, _, _, GetRoleInput>(
state,
|State(state): State<Arc<AppState>>, Json(input): Json<GetRoleInput>| async move {
let client = state
.enterprise_client_for_profile(input.profile.as_deref())
.await
.map_err(|e| {
ToolError::new(format!("Failed to get Enterprise client: {}", e))
})?;
let handler = RolesHandler::new(client);
let role = handler
.get(input.uid)
.await
.map_err(|e| ToolError::new(format!("Failed to get role: {}", e)))?;
CallToolResult::from_serialize(&role)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct ListRedisAclsInput {
#[serde(default)]
pub profile: Option<String>,
}
pub fn list_redis_acls(state: Arc<AppState>) -> Tool {
ToolBuilder::new("list_enterprise_acls")
.description(
"List all Redis ACLs in the Redis Enterprise cluster. Returns ACL names, \
rules, and associated databases.",
)
.read_only()
.idempotent()
.extractor_handler_typed::<_, _, _, ListRedisAclsInput>(
state,
|State(state): State<Arc<AppState>>, Json(input): Json<ListRedisAclsInput>| async move {
let client = state
.enterprise_client_for_profile(input.profile.as_deref())
.await
.map_err(|e| {
ToolError::new(format!("Failed to get Enterprise client: {}", e))
})?;
let handler = RedisAclHandler::new(client);
let acls = handler
.list()
.await
.map_err(|e| ToolError::new(format!("Failed to list ACLs: {}", e)))?;
wrap_list("acls", &acls)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct GetRedisAclInput {
#[serde(default)]
pub profile: Option<String>,
pub uid: u32,
}
pub fn get_redis_acl(state: Arc<AppState>) -> Tool {
ToolBuilder::new("get_enterprise_acl")
.description(
"Get detailed information about a specific Redis ACL including the ACL rule string \
and associated databases.",
)
.read_only()
.idempotent()
.extractor_handler_typed::<_, _, _, GetRedisAclInput>(
state,
|State(state): State<Arc<AppState>>, Json(input): Json<GetRedisAclInput>| async move {
let client = state
.enterprise_client_for_profile(input.profile.as_deref())
.await
.map_err(|e| {
ToolError::new(format!("Failed to get Enterprise client: {}", e))
})?;
let handler = RedisAclHandler::new(client);
let acl = handler
.get(input.uid)
.await
.map_err(|e| ToolError::new(format!("Failed to get ACL: {}", e)))?;
CallToolResult::from_serialize(&acl)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct UpdateLicenseInput {
#[serde(default)]
pub profile: Option<String>,
pub license_key: String,
}
pub fn update_license(state: Arc<AppState>) -> Tool {
ToolBuilder::new("update_enterprise_license")
.description(
"Update the Redis Enterprise cluster license with a new license key. \
This applies a new license to the cluster. Requires write permission.",
)
.extractor_handler_typed::<_, _, _, UpdateLicenseInput>(
state,
|State(state): State<Arc<AppState>>, Json(input): Json<UpdateLicenseInput>| async move {
if !state.is_write_allowed() {
return Err(McpError::tool(
"Write operations not allowed in read-only mode",
));
}
let client = state
.enterprise_client_for_profile(input.profile.as_deref())
.await
.map_err(|e| {
ToolError::new(format!("Failed to get Enterprise client: {}", e))
})?;
let handler = LicenseHandler::new(client);
let request = LicenseUpdateRequest {
license: input.license_key,
};
let license = handler
.update(request)
.await
.map_err(|e| ToolError::new(format!("Failed to update license: {}", e)))?;
CallToolResult::from_serialize(&license)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct ValidateLicenseInput {
#[serde(default)]
pub profile: Option<String>,
pub license_key: String,
}
pub fn validate_license(state: Arc<AppState>) -> Tool {
ToolBuilder::new("validate_enterprise_license")
.description(
"Validate a license key before applying it to the Redis Enterprise cluster. \
Returns license information if valid, or an error if invalid. \
This is a dry-run that does not modify the cluster.",
)
.read_only()
.idempotent()
.extractor_handler_typed::<_, _, _, ValidateLicenseInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<ValidateLicenseInput>| async move {
let client = state
.enterprise_client_for_profile(input.profile.as_deref())
.await
.map_err(|e| ToolError::new(format!("Failed to get Enterprise client: {}", e)))?;
let handler = LicenseHandler::new(client);
let license = handler
.validate(&input.license_key)
.await
.map_err(|e| ToolError::new(format!("License validation failed: {}", e)))?;
CallToolResult::from_serialize(&license)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct UpdateClusterInput {
#[serde(default)]
pub profile: Option<String>,
pub updates: Value,
}
pub fn update_cluster(state: Arc<AppState>) -> Tool {
ToolBuilder::new("update_enterprise_cluster")
.description(
"Update Redis Enterprise cluster configuration settings. \
Pass a JSON object with the fields to update (e.g., name, email_alerts, rack_aware). \
Requires write permission.",
)
.extractor_handler_typed::<_, _, _, UpdateClusterInput>(
state,
|State(state): State<Arc<AppState>>, Json(input): Json<UpdateClusterInput>| async move {
if !state.is_write_allowed() {
return Err(McpError::tool(
"Write operations not allowed in read-only mode",
));
}
let client = state
.enterprise_client_for_profile(input.profile.as_deref())
.await
.map_err(|e| {
ToolError::new(format!("Failed to get Enterprise client: {}", e))
})?;
let handler = ClusterHandler::new(client);
let result = handler
.update(input.updates)
.await
.map_err(|e| ToolError::new(format!("Failed to update cluster: {}", e)))?;
CallToolResult::from_serialize(&result)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct GetClusterPolicyInput {
#[serde(default)]
pub profile: Option<String>,
}
pub fn get_cluster_policy(state: Arc<AppState>) -> Tool {
ToolBuilder::new("get_enterprise_cluster_policy")
.description(
"Get Redis Enterprise cluster policy settings including default shards placement, \
rack awareness, default Redis version, and other cluster-wide defaults.",
)
.read_only()
.idempotent()
.extractor_handler_typed::<_, _, _, GetClusterPolicyInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<GetClusterPolicyInput>| async move {
let client = state
.enterprise_client_for_profile(input.profile.as_deref())
.await
.map_err(|e| ToolError::new(format!("Failed to get Enterprise client: {}", e)))?;
let handler = ClusterHandler::new(client);
let policy = handler
.policy()
.await
.map_err(|e| ToolError::new(format!("Failed to get cluster policy: {}", e)))?;
CallToolResult::from_serialize(&policy)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct UpdateClusterPolicyInput {
#[serde(default)]
pub profile: Option<String>,
pub policy: Value,
}
pub fn update_cluster_policy(state: Arc<AppState>) -> Tool {
ToolBuilder::new("update_enterprise_cluster_policy")
.description(
"Update Redis Enterprise cluster policy settings. \
Common settings: default_shards_placement (dense/sparse), rack_aware, \
default_provisioned_redis_version, persistent_node_removal. \
Requires write permission.",
)
.extractor_handler_typed::<_, _, _, UpdateClusterPolicyInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<UpdateClusterPolicyInput>| async move {
if !state.is_write_allowed() {
return Err(McpError::tool(
"Write operations not allowed in read-only mode",
));
}
let client = state
.enterprise_client_for_profile(input.profile.as_deref())
.await
.map_err(|e| ToolError::new(format!("Failed to get Enterprise client: {}", e)))?;
let handler = ClusterHandler::new(client);
let result = handler
.policy_update(input.policy)
.await
.map_err(|e| ToolError::new(format!("Failed to update cluster policy: {}", e)))?;
CallToolResult::from_serialize(&result)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct EnableMaintenanceModeInput {
#[serde(default)]
pub profile: Option<String>,
}
pub fn enable_maintenance_mode(state: Arc<AppState>) -> Tool {
ToolBuilder::new("enable_enterprise_maintenance_mode")
.description(
"Enable maintenance mode on the Redis Enterprise cluster. \
When enabled, cluster configuration changes are blocked, allowing safe \
maintenance operations like upgrades. Requires write permission.",
)
.extractor_handler_typed::<_, _, _, EnableMaintenanceModeInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<EnableMaintenanceModeInput>| async move {
if !state.is_write_allowed() {
return Err(McpError::tool(
"Write operations not allowed in read-only mode",
));
}
let client = state
.enterprise_client_for_profile(input.profile.as_deref())
.await
.map_err(|e| ToolError::new(format!("Failed to get Enterprise client: {}", e)))?;
let handler = ClusterHandler::new(client);
let result = handler
.update(serde_json::json!({"block_cluster_changes": true}))
.await
.map_err(|e| {
ToolError::new(format!("Failed to enable maintenance mode: {}", e))
})?;
CallToolResult::from_serialize(&serde_json::json!({
"message": "Maintenance mode enabled",
"result": result
}))
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct DisableMaintenanceModeInput {
#[serde(default)]
pub profile: Option<String>,
}
pub fn disable_maintenance_mode(state: Arc<AppState>) -> Tool {
ToolBuilder::new("disable_enterprise_maintenance_mode")
.description(
"Disable maintenance mode on the Redis Enterprise cluster. \
This re-enables cluster configuration changes after maintenance is complete. \
Requires write permission.",
)
.extractor_handler_typed::<_, _, _, DisableMaintenanceModeInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<DisableMaintenanceModeInput>| async move {
if !state.is_write_allowed() {
return Err(McpError::tool(
"Write operations not allowed in read-only mode",
));
}
let client = state
.enterprise_client_for_profile(input.profile.as_deref())
.await
.map_err(|e| ToolError::new(format!("Failed to get Enterprise client: {}", e)))?;
let handler = ClusterHandler::new(client);
let result = handler
.update(serde_json::json!({"block_cluster_changes": false}))
.await
.map_err(|e| {
ToolError::new(format!("Failed to disable maintenance mode: {}", e))
})?;
CallToolResult::from_serialize(&serde_json::json!({
"message": "Maintenance mode disabled",
"result": result
}))
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct GetClusterCertificatesInput {
#[serde(default)]
pub profile: Option<String>,
}
pub fn get_cluster_certificates(state: Arc<AppState>) -> Tool {
ToolBuilder::new("get_enterprise_cluster_certificates")
.description(
"Get all certificates configured on the Redis Enterprise cluster including \
proxy certificates, syncer certificates, and API certificates.",
)
.read_only()
.idempotent()
.extractor_handler_typed::<_, _, _, GetClusterCertificatesInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<GetClusterCertificatesInput>| async move {
let client = state
.enterprise_client_for_profile(input.profile.as_deref())
.await
.map_err(|e| ToolError::new(format!("Failed to get Enterprise client: {}", e)))?;
let handler = ClusterHandler::new(client);
let certificates = handler
.certificates()
.await
.map_err(|e| ToolError::new(format!("Failed to get certificates: {}", e)))?;
CallToolResult::from_serialize(&certificates)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct RotateClusterCertificatesInput {
#[serde(default)]
pub profile: Option<String>,
}
pub fn rotate_cluster_certificates(state: Arc<AppState>) -> Tool {
ToolBuilder::new("rotate_enterprise_cluster_certificates")
.description(
"Rotate all certificates on the Redis Enterprise cluster. \
This generates new certificates and replaces the existing ones. \
Requires write permission.",
)
.extractor_handler_typed::<_, _, _, RotateClusterCertificatesInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<RotateClusterCertificatesInput>| async move {
if !state.is_write_allowed() {
return Err(McpError::tool(
"Write operations not allowed in read-only mode",
));
}
let client = state
.enterprise_client_for_profile(input.profile.as_deref())
.await
.map_err(|e| {
ToolError::new(format!("Failed to get Enterprise client: {}", e))
})?;
let handler = ClusterHandler::new(client);
let result = handler
.certificates_rotate()
.await
.map_err(|e| ToolError::new(format!("Failed to rotate certificates: {}", e)))?;
CallToolResult::from_serialize(&serde_json::json!({
"message": "Certificate rotation initiated",
"result": result
}))
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct UpdateClusterCertificatesInput {
#[serde(default)]
pub profile: Option<String>,
pub name: String,
pub certificate: String,
pub key: String,
}
pub fn update_cluster_certificates(state: Arc<AppState>) -> Tool {
ToolBuilder::new("update_enterprise_cluster_certificates")
.description(
"Update a specific certificate on the Redis Enterprise cluster. \
Provide the certificate name (proxy, syncer, api), the PEM-encoded certificate, \
and the PEM-encoded private key. Requires write permission.",
)
.extractor_handler_typed::<_, _, _, UpdateClusterCertificatesInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<UpdateClusterCertificatesInput>| async move {
if !state.is_write_allowed() {
return Err(McpError::tool(
"Write operations not allowed in read-only mode",
));
}
let client = state
.enterprise_client_for_profile(input.profile.as_deref())
.await
.map_err(|e| {
ToolError::new(format!("Failed to get Enterprise client: {}", e))
})?;
let handler = ClusterHandler::new(client);
let body = serde_json::json!({
"name": input.name,
"certificate": input.certificate,
"key": input.key
});
let result = handler
.update_cert(body)
.await
.map_err(|e| ToolError::new(format!("Failed to update certificate: {}", e)))?;
CallToolResult::from_serialize(&serde_json::json!({
"message": "Certificate updated successfully",
"name": input.name,
"result": result
}))
},
)
.build()
}
pub fn instructions() -> &'static str {
r#"
### Redis Enterprise - Cluster
- get_cluster: Get cluster information
- get_cluster_stats: Get cluster statistics
- update_enterprise_cluster: Update cluster configuration (write)
- get_enterprise_cluster_policy: Get cluster policy settings
- update_enterprise_cluster_policy: Update cluster policy (write)
- enable_enterprise_maintenance_mode: Enable maintenance mode (write)
- disable_enterprise_maintenance_mode: Disable maintenance mode (write)
- get_enterprise_cluster_certificates: Get cluster certificates
- rotate_enterprise_cluster_certificates: Rotate all certificates (write)
- update_enterprise_cluster_certificates: Update a specific certificate (write)
### Redis Enterprise - License
- get_license: Get license information (type, expiration, features)
- get_license_usage: Get license utilization (shards, nodes, RAM vs limits)
- update_enterprise_license: Update cluster license with a new key (write)
- validate_enterprise_license: Validate a license key before applying
### Redis Enterprise - Logs
- list_logs: List cluster event logs (with time range and pagination)
### Redis Enterprise - Databases
- list_enterprise_databases: List all databases
- get_enterprise_database: Get database details
- get_database_stats: Get database statistics
- get_database_endpoints: Get connection endpoints
- list_database_alerts: Get alerts for a database
### Redis Enterprise - Nodes
- list_nodes: List cluster nodes
- get_node: Get node details
- get_node_stats: Get node statistics
### Redis Enterprise - Users & Alerts
- list_enterprise_users: List cluster users
- get_enterprise_user: Get user details
- list_alerts: List all active alerts
### Redis Enterprise - Shards
- list_shards: List database shards (with optional database filter)
- get_shard: Get shard details by UID
### Redis Enterprise - Aggregate Stats
- get_all_nodes_stats: Get stats for all nodes in one call
- get_all_databases_stats: Get stats for all databases in one call
- get_shard_stats: Get stats for a specific shard
- get_all_shards_stats: Get stats for all shards in one call
### Redis Enterprise - Debug Info
- list_debug_info_tasks: List debug info collection tasks
- get_debug_info_status: Get status of a debug info collection task
### Redis Enterprise - Modules
- list_modules: List installed Redis modules (RedisJSON, RediSearch, etc.)
- get_module: Get details about a specific module
### Redis Enterprise - Roles
- list_enterprise_roles: List all roles in the cluster
- get_enterprise_role: Get role details and permissions
### Redis Enterprise - ACLs
- list_enterprise_acls: List all Redis ACLs
- get_enterprise_acl: Get ACL details and rules
### Redis Enterprise - Write Operations (require --read-only=false)
- backup_enterprise_database: Trigger a database backup and wait for completion
- import_enterprise_database: Import data into a database and wait for completion
- create_enterprise_database: Create a new database
- update_enterprise_database: Update database configuration
- delete_enterprise_database: Delete a database
- flush_enterprise_database: Flush all data from a database
"#
}
pub fn router(state: Arc<AppState>) -> McpRouter {
McpRouter::new()
.tool(get_cluster(state.clone()))
.tool(get_cluster_stats(state.clone()))
.tool(update_cluster(state.clone()))
.tool(get_cluster_policy(state.clone()))
.tool(update_cluster_policy(state.clone()))
.tool(enable_maintenance_mode(state.clone()))
.tool(disable_maintenance_mode(state.clone()))
.tool(get_cluster_certificates(state.clone()))
.tool(rotate_cluster_certificates(state.clone()))
.tool(update_cluster_certificates(state.clone()))
.tool(get_license(state.clone()))
.tool(get_license_usage(state.clone()))
.tool(update_license(state.clone()))
.tool(validate_license(state.clone()))
.tool(list_logs(state.clone()))
.tool(list_databases(state.clone()))
.tool(get_database(state.clone()))
.tool(get_database_stats(state.clone()))
.tool(get_database_endpoints(state.clone()))
.tool(list_database_alerts(state.clone()))
.tool(list_nodes(state.clone()))
.tool(get_node(state.clone()))
.tool(get_node_stats(state.clone()))
.tool(list_users(state.clone()))
.tool(get_user(state.clone()))
.tool(list_alerts(state.clone()))
.tool(list_shards(state.clone()))
.tool(get_shard(state.clone()))
.tool(get_all_nodes_stats(state.clone()))
.tool(get_all_databases_stats(state.clone()))
.tool(get_shard_stats(state.clone()))
.tool(get_all_shards_stats(state.clone()))
.tool(list_debug_info_tasks(state.clone()))
.tool(get_debug_info_status(state.clone()))
.tool(list_modules(state.clone()))
.tool(get_module(state.clone()))
.tool(list_roles(state.clone()))
.tool(get_role(state.clone()))
.tool(list_redis_acls(state.clone()))
.tool(get_redis_acl(state.clone()))
.tool(backup_enterprise_database(state.clone()))
.tool(import_enterprise_database(state.clone()))
.tool(create_enterprise_database(state.clone()))
.tool(update_enterprise_database(state.clone()))
.tool(delete_enterprise_database(state.clone()))
.tool(flush_enterprise_database(state.clone()))
}