use std::sync::Arc;
use redis_cloud::fixed::databases::{
DatabaseTagCreateRequest, DatabaseTagUpdateRequest, DatabaseTagsUpdateRequest,
FixedDatabaseBackupRequest, FixedDatabaseCreateRequest, FixedDatabaseHandler,
FixedDatabaseImportRequest, FixedDatabaseUpdateRequest, Tag,
};
use redis_cloud::fixed::subscriptions::{
FixedSubscriptionCreateRequest, FixedSubscriptionHandler, FixedSubscriptionUpdateRequest,
};
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;
#[derive(Debug, Deserialize, JsonSchema)]
pub struct ListFixedSubscriptionsInput {
#[serde(default)]
pub profile: Option<String>,
}
pub fn list_fixed_subscriptions(state: Arc<AppState>) -> Tool {
ToolBuilder::new("list_fixed_subscriptions")
.description(
"List all Redis Cloud Fixed/Essentials subscriptions in the current account.",
)
.read_only_safe()
.extractor_handler_typed::<_, _, _, ListFixedSubscriptionsInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<ListFixedSubscriptionsInput>| 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 = FixedSubscriptionHandler::new(client);
let subscriptions = handler
.list()
.await
.tool_context("Failed to list fixed subscriptions")?;
CallToolResult::from_serialize(&subscriptions)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct GetFixedSubscriptionInput {
pub subscription_id: i32,
#[serde(default)]
pub profile: Option<String>,
}
pub fn get_fixed_subscription(state: Arc<AppState>) -> Tool {
ToolBuilder::new("get_fixed_subscription")
.description(
"Get detailed information about a specific Redis Cloud Fixed/Essentials subscription.",
)
.read_only_safe()
.extractor_handler_typed::<_, _, _, GetFixedSubscriptionInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<GetFixedSubscriptionInput>| 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 = FixedSubscriptionHandler::new(client);
let subscription = handler
.get_by_id(input.subscription_id)
.await
.tool_context("Failed to get fixed subscription")?;
CallToolResult::from_serialize(&subscription)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct CreateFixedSubscriptionInput {
pub name: String,
pub plan_id: i32,
#[serde(default)]
pub payment_method: Option<String>,
#[serde(default)]
pub payment_method_id: Option<i32>,
#[serde(default)]
pub profile: Option<String>,
}
pub fn create_fixed_subscription(state: Arc<AppState>) -> Tool {
ToolBuilder::new("create_fixed_subscription")
.description(
"Create a new Redis Cloud Fixed/Essentials subscription. \
Requires write permission.",
)
.non_destructive()
.extractor_handler_typed::<_, _, _, CreateFixedSubscriptionInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<CreateFixedSubscriptionInput>| 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 request = FixedSubscriptionCreateRequest {
name: input.name,
plan_id: input.plan_id,
payment_method: input.payment_method,
payment_method_id: input.payment_method_id,
command_type: None,
};
let handler = FixedSubscriptionHandler::new(client);
let result = handler
.create(&request)
.await
.tool_context("Failed to create fixed subscription")?;
CallToolResult::from_serialize(&result)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct UpdateFixedSubscriptionInput {
pub subscription_id: i32,
#[serde(default)]
pub name: Option<String>,
#[serde(default)]
pub plan_id: Option<i32>,
#[serde(default)]
pub payment_method: Option<String>,
#[serde(default)]
pub payment_method_id: Option<i32>,
#[serde(default)]
pub profile: Option<String>,
}
pub fn update_fixed_subscription(state: Arc<AppState>) -> Tool {
ToolBuilder::new("update_fixed_subscription")
.description(
"Update a Redis Cloud Fixed/Essentials subscription. \
Requires write permission.",
)
.non_destructive()
.extractor_handler_typed::<_, _, _, UpdateFixedSubscriptionInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<UpdateFixedSubscriptionInput>| 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 request = FixedSubscriptionUpdateRequest {
subscription_id: None,
name: input.name,
plan_id: input.plan_id,
payment_method: input.payment_method,
payment_method_id: input.payment_method_id,
command_type: None,
};
let handler = FixedSubscriptionHandler::new(client);
let result = handler
.update(input.subscription_id, &request)
.await
.tool_context("Failed to update fixed subscription")?;
CallToolResult::from_serialize(&result)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct DeleteFixedSubscriptionInput {
pub subscription_id: i32,
#[serde(default)]
pub profile: Option<String>,
}
pub fn delete_fixed_subscription(state: Arc<AppState>) -> Tool {
ToolBuilder::new("delete_fixed_subscription")
.description(
"DANGEROUS: Permanently deletes a Fixed/Essentials subscription. \
All databases must be deleted first. This action cannot be undone. \
Requires write permission.",
)
.destructive()
.extractor_handler_typed::<_, _, _, DeleteFixedSubscriptionInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<DeleteFixedSubscriptionInput>| 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 = FixedSubscriptionHandler::new(client);
let result = handler
.delete_by_id(input.subscription_id)
.await
.tool_context("Failed to delete fixed subscription")?;
CallToolResult::from_serialize(&result)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct ListFixedPlansInput {
#[serde(default)]
pub provider: Option<String>,
#[serde(default)]
pub redis_flex: Option<bool>,
#[serde(default)]
pub profile: Option<String>,
}
pub fn list_fixed_plans(state: Arc<AppState>) -> Tool {
ToolBuilder::new("list_fixed_plans")
.description(
"List available Redis Cloud Fixed/Essentials plans. \
Plans describe dataset size, cloud provider, region, and pricing.",
)
.read_only_safe()
.extractor_handler_typed::<_, _, _, ListFixedPlansInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<ListFixedPlansInput>| 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 = FixedSubscriptionHandler::new(client);
let plans = handler
.list_plans(input.provider, input.redis_flex)
.await
.tool_context("Failed to list fixed plans")?;
CallToolResult::from_serialize(&plans)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct GetFixedPlansBySubscriptionInput {
pub subscription_id: i32,
#[serde(default)]
pub profile: Option<String>,
}
pub fn get_fixed_plans_by_subscription(state: Arc<AppState>) -> Tool {
ToolBuilder::new("get_fixed_plans_by_subscription")
.description(
"Get compatible Fixed/Essentials plans for a specific subscription. \
Useful when upgrading or changing a subscription's plan.",
)
.read_only_safe()
.extractor_handler_typed::<_, _, _, GetFixedPlansBySubscriptionInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<GetFixedPlansBySubscriptionInput>| 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 = FixedSubscriptionHandler::new(client);
let plans = handler
.get_plans_by_subscription_id(input.subscription_id)
.await
.tool_context("Failed to get fixed plans by subscription")?;
CallToolResult::from_serialize(&plans)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct GetFixedPlanInput {
pub plan_id: i32,
#[serde(default)]
pub profile: Option<String>,
}
pub fn get_fixed_plan(state: Arc<AppState>) -> Tool {
ToolBuilder::new("get_fixed_plan")
.description(
"Get detailed information about a specific Fixed/Essentials plan \
including pricing, capacity, and feature support.",
)
.read_only_safe()
.extractor_handler_typed::<_, _, _, GetFixedPlanInput>(
state,
|State(state): State<Arc<AppState>>, Json(input): Json<GetFixedPlanInput>| 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 = FixedSubscriptionHandler::new(client);
let plan = handler
.get_plan_by_id(input.plan_id)
.await
.tool_context("Failed to get fixed plan")?;
CallToolResult::from_serialize(&plan)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct GetFixedRedisVersionsInput {
pub subscription_id: i32,
#[serde(default)]
pub profile: Option<String>,
}
pub fn get_fixed_redis_versions(state: Arc<AppState>) -> Tool {
ToolBuilder::new("get_fixed_redis_versions")
.description(
"Get available Redis database versions for a specific Fixed/Essentials subscription.",
)
.read_only_safe()
.extractor_handler_typed::<_, _, _, GetFixedRedisVersionsInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<GetFixedRedisVersionsInput>| 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 = FixedSubscriptionHandler::new(client);
let versions = handler
.get_redis_versions(input.subscription_id)
.await
.tool_context("Failed to get fixed Redis versions")?;
CallToolResult::from_serialize(&versions)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct ListFixedDatabasesInput {
pub subscription_id: i32,
#[serde(default)]
pub offset: Option<i32>,
#[serde(default)]
pub limit: Option<i32>,
#[serde(default)]
pub profile: Option<String>,
}
pub fn list_fixed_databases(state: Arc<AppState>) -> Tool {
ToolBuilder::new("list_fixed_databases")
.description(
"List all databases in a Redis Cloud Fixed/Essentials subscription.",
)
.read_only_safe()
.extractor_handler_typed::<_, _, _, ListFixedDatabasesInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<ListFixedDatabasesInput>| 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 = FixedDatabaseHandler::new(client);
let databases = handler
.list(input.subscription_id, input.offset, input.limit)
.await
.tool_context("Failed to list fixed databases")?;
CallToolResult::from_serialize(&databases)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct GetFixedDatabaseInput {
pub subscription_id: i32,
pub database_id: i32,
#[serde(default)]
pub profile: Option<String>,
}
pub fn get_fixed_database(state: Arc<AppState>) -> Tool {
ToolBuilder::new("get_fixed_database")
.description(
"Get detailed information about a specific database in a \
Redis Cloud Fixed/Essentials subscription.",
)
.read_only_safe()
.extractor_handler_typed::<_, _, _, GetFixedDatabaseInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<GetFixedDatabaseInput>| 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 = FixedDatabaseHandler::new(client);
let database = handler
.get_by_id(input.subscription_id, input.database_id)
.await
.tool_context("Failed to get fixed database")?;
CallToolResult::from_serialize(&database)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct CreateFixedDatabaseInput {
pub subscription_id: i32,
pub name: String,
#[serde(default)]
pub protocol: Option<String>,
#[serde(default)]
pub memory_limit_in_gb: Option<f64>,
#[serde(default)]
pub dataset_size_in_gb: Option<f64>,
#[serde(default)]
pub support_oss_cluster_api: Option<bool>,
#[serde(default)]
pub redis_version: Option<String>,
#[serde(default)]
pub data_persistence: Option<String>,
#[serde(default)]
pub data_eviction_policy: Option<String>,
#[serde(default)]
pub replication: Option<bool>,
#[serde(default)]
pub enable_tls: Option<bool>,
#[serde(default)]
pub password: Option<String>,
#[serde(default)]
pub source_ips: Option<Vec<String>>,
#[serde(default)]
pub profile: Option<String>,
}
pub fn create_fixed_database(state: Arc<AppState>) -> Tool {
ToolBuilder::new("create_fixed_database")
.description(
"Create a new database in a Redis Cloud Fixed/Essentials subscription. \
Requires write permission.",
)
.non_destructive()
.extractor_handler_typed::<_, _, _, CreateFixedDatabaseInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<CreateFixedDatabaseInput>| 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 request = FixedDatabaseCreateRequest {
subscription_id: None,
name: input.name,
protocol: input.protocol,
memory_limit_in_gb: input.memory_limit_in_gb,
dataset_size_in_gb: input.dataset_size_in_gb,
support_oss_cluster_api: input.support_oss_cluster_api,
redis_version: input.redis_version,
resp_version: None,
use_external_endpoint_for_oss_cluster_api: None,
enable_database_clustering: None,
number_of_shards: None,
data_persistence: input.data_persistence,
data_eviction_policy: input.data_eviction_policy,
replication: input.replication,
periodic_backup_path: None,
source_ips: input.source_ips,
regex_rules: None,
replica_of: None,
replica: None,
client_ssl_certificate: None,
client_tls_certificates: None,
enable_tls: input.enable_tls,
password: input.password,
alerts: None,
modules: None,
command_type: None,
};
let handler = FixedDatabaseHandler::new(client);
let result = handler
.create(input.subscription_id, &request)
.await
.tool_context("Failed to create fixed database")?;
CallToolResult::from_serialize(&result)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct UpdateFixedDatabaseInput {
pub subscription_id: i32,
pub database_id: i32,
#[serde(default)]
pub name: Option<String>,
#[serde(default)]
pub memory_limit_in_gb: Option<f64>,
#[serde(default)]
pub data_persistence: Option<String>,
#[serde(default)]
pub data_eviction_policy: Option<String>,
#[serde(default)]
pub replication: Option<bool>,
#[serde(default)]
pub enable_tls: Option<bool>,
#[serde(default)]
pub password: Option<String>,
#[serde(default)]
pub source_ips: Option<Vec<String>>,
#[serde(default)]
pub profile: Option<String>,
}
pub fn update_fixed_database(state: Arc<AppState>) -> Tool {
ToolBuilder::new("update_fixed_database")
.description(
"Update a database in a Redis Cloud Fixed/Essentials subscription. \
Requires write permission.",
)
.non_destructive()
.extractor_handler_typed::<_, _, _, UpdateFixedDatabaseInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<UpdateFixedDatabaseInput>| 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 request = FixedDatabaseUpdateRequest {
subscription_id: None,
database_id: None,
name: input.name,
memory_limit_in_gb: input.memory_limit_in_gb,
dataset_size_in_gb: None,
support_oss_cluster_api: None,
resp_version: None,
use_external_endpoint_for_oss_cluster_api: None,
enable_database_clustering: None,
number_of_shards: None,
data_persistence: input.data_persistence,
data_eviction_policy: input.data_eviction_policy,
replication: input.replication,
periodic_backup_path: None,
source_ips: input.source_ips,
replica_of: None,
replica: None,
regex_rules: None,
client_ssl_certificate: None,
client_tls_certificates: None,
enable_tls: input.enable_tls,
password: input.password,
enable_default_user: None,
alerts: None,
command_type: None,
};
let handler = FixedDatabaseHandler::new(client);
let result = handler
.update(input.subscription_id, input.database_id, &request)
.await
.tool_context("Failed to update fixed database")?;
CallToolResult::from_serialize(&result)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct DeleteFixedDatabaseInput {
pub subscription_id: i32,
pub database_id: i32,
#[serde(default)]
pub profile: Option<String>,
}
pub fn delete_fixed_database(state: Arc<AppState>) -> Tool {
ToolBuilder::new("delete_fixed_database")
.description(
"DANGEROUS: Permanently deletes a Fixed/Essentials database and all its data. \
This action cannot be undone. Requires write permission.",
)
.destructive()
.extractor_handler_typed::<_, _, _, DeleteFixedDatabaseInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<DeleteFixedDatabaseInput>| 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 = FixedDatabaseHandler::new(client);
let result = handler
.delete_by_id(input.subscription_id, input.database_id)
.await
.tool_context("Failed to delete fixed database")?;
CallToolResult::from_serialize(&result)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct GetFixedDatabaseBackupStatusInput {
pub subscription_id: i32,
pub database_id: i32,
#[serde(default)]
pub profile: Option<String>,
}
pub fn get_fixed_database_backup_status(state: Arc<AppState>) -> Tool {
ToolBuilder::new("get_fixed_database_backup_status")
.description("Get the latest backup status for a Fixed/Essentials database.")
.read_only_safe()
.extractor_handler_typed::<_, _, _, GetFixedDatabaseBackupStatusInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<GetFixedDatabaseBackupStatusInput>| 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 = FixedDatabaseHandler::new(client);
let status = handler
.get_backup_status(input.subscription_id, input.database_id)
.await
.tool_context("Failed to get fixed database backup status")?;
CallToolResult::from_serialize(&status)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct BackupFixedDatabaseInput {
pub subscription_id: i32,
pub database_id: i32,
#[serde(default)]
pub adhoc_backup_path: Option<String>,
#[serde(default)]
pub profile: Option<String>,
}
pub fn backup_fixed_database(state: Arc<AppState>) -> Tool {
ToolBuilder::new("backup_fixed_database")
.description(
"Trigger a manual backup of a Fixed/Essentials database. \
Requires write permission.",
)
.non_destructive()
.extractor_handler_typed::<_, _, _, BackupFixedDatabaseInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<BackupFixedDatabaseInput>| 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 request = FixedDatabaseBackupRequest {
subscription_id: None,
database_id: None,
adhoc_backup_path: input.adhoc_backup_path,
command_type: None,
};
let handler = FixedDatabaseHandler::new(client);
let result = handler
.backup(input.subscription_id, input.database_id, &request)
.await
.tool_context("Failed to backup fixed database")?;
CallToolResult::from_serialize(&result)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct GetFixedDatabaseImportStatusInput {
pub subscription_id: i32,
pub database_id: i32,
#[serde(default)]
pub profile: Option<String>,
}
pub fn get_fixed_database_import_status(state: Arc<AppState>) -> Tool {
ToolBuilder::new("get_fixed_database_import_status")
.description("Get the latest import status for a Fixed/Essentials database.")
.read_only_safe()
.extractor_handler_typed::<_, _, _, GetFixedDatabaseImportStatusInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<GetFixedDatabaseImportStatusInput>| 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 = FixedDatabaseHandler::new(client);
let status = handler
.get_import_status(input.subscription_id, input.database_id)
.await
.tool_context("Failed to get fixed database import status")?;
CallToolResult::from_serialize(&status)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct ImportFixedDatabaseInput {
pub subscription_id: i32,
pub database_id: i32,
pub source_type: String,
pub import_from_uri: Vec<String>,
#[serde(default)]
pub profile: Option<String>,
}
pub fn import_fixed_database(state: Arc<AppState>) -> Tool {
ToolBuilder::new("import_fixed_database")
.description(
"Import data into a Fixed/Essentials database from an external source. \
WARNING: This will overwrite existing data. Requires write permission.",
)
.non_destructive()
.extractor_handler_typed::<_, _, _, ImportFixedDatabaseInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<ImportFixedDatabaseInput>| 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 request = FixedDatabaseImportRequest {
subscription_id: None,
database_id: None,
source_type: input.source_type,
import_from_uri: input.import_from_uri,
command_type: None,
};
let handler = FixedDatabaseHandler::new(client);
let result = handler
.import(input.subscription_id, input.database_id, &request)
.await
.tool_context("Failed to import fixed database")?;
CallToolResult::from_serialize(&result)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct GetFixedDatabaseSlowLogInput {
pub subscription_id: i32,
pub database_id: i32,
#[serde(default)]
pub profile: Option<String>,
}
pub fn get_fixed_database_slow_log(state: Arc<AppState>) -> Tool {
ToolBuilder::new("get_fixed_database_slow_log")
.description(
"Get slow log entries for a Fixed/Essentials database. \
Shows slow queries for debugging performance issues.",
)
.read_only_safe()
.extractor_handler_typed::<_, _, _, GetFixedDatabaseSlowLogInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<GetFixedDatabaseSlowLogInput>| 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 = FixedDatabaseHandler::new(client);
let log = handler
.get_slow_log(input.subscription_id, input.database_id)
.await
.tool_context("Failed to get fixed database slow log")?;
CallToolResult::from_serialize(&log)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct GetFixedDatabaseTagsInput {
pub subscription_id: i32,
pub database_id: i32,
#[serde(default)]
pub profile: Option<String>,
}
pub fn get_fixed_database_tags(state: Arc<AppState>) -> Tool {
ToolBuilder::new("get_fixed_database_tags")
.description("Get tags attached to a Fixed/Essentials database.")
.read_only_safe()
.extractor_handler_typed::<_, _, _, GetFixedDatabaseTagsInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<GetFixedDatabaseTagsInput>| 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 = FixedDatabaseHandler::new(client);
let tags = handler
.get_tags(input.subscription_id, input.database_id)
.await
.tool_context("Failed to get fixed database tags")?;
CallToolResult::from_serialize(&tags)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct CreateFixedDatabaseTagInput {
pub subscription_id: i32,
pub database_id: i32,
pub key: String,
pub value: String,
#[serde(default)]
pub profile: Option<String>,
}
pub fn create_fixed_database_tag(state: Arc<AppState>) -> Tool {
ToolBuilder::new("create_fixed_database_tag")
.description(
"Create a tag on a Fixed/Essentials database. \
Requires write permission.",
)
.non_destructive()
.extractor_handler_typed::<_, _, _, CreateFixedDatabaseTagInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<CreateFixedDatabaseTagInput>| 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 request = DatabaseTagCreateRequest {
key: input.key,
value: input.value,
subscription_id: None,
database_id: None,
command_type: None,
};
let handler = FixedDatabaseHandler::new(client);
let tag = handler
.create_tag(input.subscription_id, input.database_id, &request)
.await
.tool_context("Failed to create fixed database tag")?;
CallToolResult::from_serialize(&tag)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct UpdateFixedDatabaseTagInput {
pub subscription_id: i32,
pub database_id: i32,
pub tag_key: String,
pub value: String,
#[serde(default)]
pub profile: Option<String>,
}
pub fn update_fixed_database_tag(state: Arc<AppState>) -> Tool {
ToolBuilder::new("update_fixed_database_tag")
.description(
"Update a tag value on a Fixed/Essentials database. \
Requires write permission.",
)
.non_destructive()
.extractor_handler_typed::<_, _, _, UpdateFixedDatabaseTagInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<UpdateFixedDatabaseTagInput>| 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 request = DatabaseTagUpdateRequest {
subscription_id: None,
database_id: None,
key: None,
value: input.value,
command_type: None,
};
let handler = FixedDatabaseHandler::new(client);
let tag = handler
.update_tag(
input.subscription_id,
input.database_id,
input.tag_key,
&request,
)
.await
.tool_context("Failed to update fixed database tag")?;
CallToolResult::from_serialize(&tag)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct DeleteFixedDatabaseTagInput {
pub subscription_id: i32,
pub database_id: i32,
pub tag_key: String,
#[serde(default)]
pub profile: Option<String>,
}
pub fn delete_fixed_database_tag(state: Arc<AppState>) -> Tool {
ToolBuilder::new("delete_fixed_database_tag")
.description(
"DANGEROUS: Permanently deletes a tag from a Fixed/Essentials database. \
This action cannot be undone. Requires write permission.",
)
.destructive()
.extractor_handler_typed::<_, _, _, DeleteFixedDatabaseTagInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<DeleteFixedDatabaseTagInput>| 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 = FixedDatabaseHandler::new(client);
let result = handler
.delete_tag(input.subscription_id, input.database_id, input.tag_key)
.await
.tool_context("Failed to delete fixed database tag")?;
CallToolResult::from_serialize(&result)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct FixedTagInput {
pub key: String,
pub value: String,
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct UpdateFixedDatabaseTagsInput {
pub subscription_id: i32,
pub database_id: i32,
pub tags: Vec<FixedTagInput>,
#[serde(default)]
pub profile: Option<String>,
}
pub fn update_fixed_database_tags(state: Arc<AppState>) -> Tool {
ToolBuilder::new("update_fixed_database_tags")
.description(
"Update all tags on a Fixed/Essentials database (replaces existing tags). \
Requires write permission.",
)
.non_destructive()
.extractor_handler_typed::<_, _, _, UpdateFixedDatabaseTagsInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<UpdateFixedDatabaseTagsInput>| 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 tags = input
.tags
.into_iter()
.map(|t| Tag {
key: t.key,
value: t.value,
command_type: None,
})
.collect();
let request = DatabaseTagsUpdateRequest {
subscription_id: None,
database_id: None,
tags,
command_type: None,
};
let handler = FixedDatabaseHandler::new(client);
let result = handler
.update_tags(input.subscription_id, input.database_id, &request)
.await
.tool_context("Failed to update fixed database tags")?;
CallToolResult::from_serialize(&result)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct GetFixedDatabaseUpgradeVersionsInput {
pub subscription_id: i32,
pub database_id: i32,
#[serde(default)]
pub profile: Option<String>,
}
pub fn get_fixed_database_upgrade_versions(state: Arc<AppState>) -> Tool {
ToolBuilder::new("get_fixed_database_upgrade_versions")
.description(
"Get available target Redis versions that a Fixed/Essentials database can be upgraded to.",
)
.read_only_safe()
.extractor_handler_typed::<_, _, _, GetFixedDatabaseUpgradeVersionsInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<GetFixedDatabaseUpgradeVersionsInput>| 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 = FixedDatabaseHandler::new(client);
let versions = handler
.get_available_target_versions(input.subscription_id, input.database_id)
.await
.tool_context("Failed to get fixed database upgrade versions")?;
CallToolResult::from_serialize(&versions)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct GetFixedDatabaseUpgradeStatusInput {
pub subscription_id: i32,
pub database_id: i32,
#[serde(default)]
pub profile: Option<String>,
}
pub fn get_fixed_database_upgrade_status(state: Arc<AppState>) -> Tool {
ToolBuilder::new("get_fixed_database_upgrade_status")
.description("Get the latest Redis version upgrade status for a Fixed/Essentials database.")
.read_only_safe()
.extractor_handler_typed::<_, _, _, GetFixedDatabaseUpgradeStatusInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<GetFixedDatabaseUpgradeStatusInput>| 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 = FixedDatabaseHandler::new(client);
let status = handler
.get_upgrade_status(input.subscription_id, input.database_id)
.await
.tool_context("Failed to get fixed database upgrade status")?;
CallToolResult::from_serialize(&status)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct UpgradeFixedDatabaseRedisVersionInput {
pub subscription_id: i32,
pub database_id: i32,
pub target_version: String,
#[serde(default)]
pub profile: Option<String>,
}
pub fn upgrade_fixed_database_redis_version(state: Arc<AppState>) -> Tool {
ToolBuilder::new("upgrade_fixed_database_redis_version")
.description(
"Upgrade the Redis version of a Fixed/Essentials database. \
Requires write permission.",
)
.non_destructive()
.extractor_handler_typed::<_, _, _, UpgradeFixedDatabaseRedisVersionInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<UpgradeFixedDatabaseRedisVersionInput>| 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 = FixedDatabaseHandler::new(client);
let result = handler
.upgrade_redis_version(
input.subscription_id,
input.database_id,
&input.target_version,
)
.await
.tool_context("Failed to upgrade fixed database Redis version")?;
CallToolResult::from_serialize(&result)
},
)
.build()
}
pub fn router(state: Arc<AppState>) -> McpRouter {
McpRouter::new()
.tool(list_fixed_subscriptions(state.clone()))
.tool(get_fixed_subscription(state.clone()))
.tool(list_fixed_plans(state.clone()))
.tool(get_fixed_plans_by_subscription(state.clone()))
.tool(get_fixed_plan(state.clone()))
.tool(get_fixed_redis_versions(state.clone()))
.tool(list_fixed_databases(state.clone()))
.tool(get_fixed_database(state.clone()))
.tool(get_fixed_database_backup_status(state.clone()))
.tool(get_fixed_database_import_status(state.clone()))
.tool(get_fixed_database_slow_log(state.clone()))
.tool(get_fixed_database_tags(state.clone()))
.tool(get_fixed_database_upgrade_versions(state.clone()))
.tool(get_fixed_database_upgrade_status(state.clone()))
.tool(create_fixed_subscription(state.clone()))
.tool(update_fixed_subscription(state.clone()))
.tool(delete_fixed_subscription(state.clone()))
.tool(create_fixed_database(state.clone()))
.tool(update_fixed_database(state.clone()))
.tool(delete_fixed_database(state.clone()))
.tool(backup_fixed_database(state.clone()))
.tool(import_fixed_database(state.clone()))
.tool(create_fixed_database_tag(state.clone()))
.tool(update_fixed_database_tag(state.clone()))
.tool(delete_fixed_database_tag(state.clone()))
.tool(update_fixed_database_tags(state.clone()))
.tool(upgrade_fixed_database_redis_version(state))
}