redisctl-mcp 0.10.0

MCP (Model Context Protocol) server for Redis Cloud and Enterprise
Documentation
//! Alerts, logs, aggregate stats, shards, debug info, and module tools

use redis_enterprise::debuginfo::{DebugInfoHandler, DebugInfoRequest};
use redis_enterprise::logs::{LogsHandler, LogsQuery};
use tower_mcp::{CallToolResult, ResultExt};

use crate::tools::macros::{enterprise_tool, mcp_module};

mcp_module! {
    list_alerts => "list_alerts",
    acknowledge_enterprise_alert => "acknowledge_enterprise_alert",
    list_logs => "list_logs",
    get_all_nodes_stats => "get_all_nodes_stats",
    get_all_databases_stats => "get_all_databases_stats",
    get_shard_stats => "get_shard_stats",
    get_all_shards_stats => "get_all_shards_stats",
    list_shards => "list_shards",
    get_shard => "get_shard",
    list_shards_by_database => "list_shards_by_database",
    list_shards_by_node => "list_shards_by_node",
    list_debug_info_tasks => "list_debug_info_tasks",
    get_debug_info_status => "get_debug_info_status",
    create_debug_info => "create_debug_info",
    list_modules => "list_modules",
    get_module => "get_module",
}

// ============================================================================
// Alert tools
// ============================================================================

enterprise_tool!(read_only, list_alerts, "list_alerts",
    "List all active alerts.",
    {} => |client, _input| {
        let handler = redis_enterprise::alerts::AlertHandler::new(client);
        let alerts = handler.list().await.tool_context("Failed to list alerts")?;
        CallToolResult::from_list("alerts", &alerts)
    }
);

enterprise_tool!(write, acknowledge_enterprise_alert, "acknowledge_enterprise_alert",
    "Acknowledge (clear) a specific alert by ID.",
    {
        /// Alert UID to acknowledge
        pub alert_uid: String,
    } => |client, input| {
        let handler = redis_enterprise::alerts::AlertHandler::new(client);
        handler
            .clear(&input.alert_uid)
            .await
            .tool_context("Failed to acknowledge alert")?;

        CallToolResult::from_serialize(&serde_json::json!({
            "message": "Alert acknowledged successfully",
            "alert_uid": input.alert_uid
        }))
    }
);

// ============================================================================
// Logs tools
// ============================================================================

enterprise_tool!(read_only, list_logs, "list_logs",
    "List cluster event logs. Supports filtering by time range and pagination.",
    {
        /// Start time - only return events after this time (ISO 8601 format, e.g., "2024-01-15T10:00:00Z")
        #[serde(default)]
        pub start_time: Option<String>,
        /// End time - only return events before this time (ISO 8601 format)
        #[serde(default)]
        pub end_time: Option<String>,
        /// Sort order: "asc" (oldest first) or "desc" (newest first, default)
        #[serde(default)]
        pub order: Option<String>,
        /// Maximum number of log entries to return
        #[serde(default)]
        pub limit: Option<u32>,
        /// Number of entries to skip (for pagination)
        #[serde(default)]
        pub offset: Option<u32>,
    } => |client, input| {
        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
            .tool_context("Failed to list logs")?;

        CallToolResult::from_list("logs", &logs)
    }
);

// ============================================================================
// Aggregate Stats tools
// ============================================================================

enterprise_tool!(read_only, get_all_nodes_stats, "get_all_nodes_stats",
    "Get current statistics for all nodes including CPU, memory, and network metrics.",
    {} => |client, _input| {
        let handler = redis_enterprise::stats::StatsHandler::new(client);
        let stats = handler
            .nodes_last()
            .await
            .tool_context("Failed to get all nodes stats")?;

        CallToolResult::from_serialize(&stats)
    }
);

enterprise_tool!(read_only, get_all_databases_stats, "get_all_databases_stats",
    "Get current statistics for all databases including latency, throughput, and memory usage.",
    {} => |client, _input| {
        let handler = redis_enterprise::stats::StatsHandler::new(client);
        let stats = handler
            .databases_last()
            .await
            .tool_context("Failed to get all databases stats")?;

        CallToolResult::from_serialize(&stats)
    }
);

enterprise_tool!(read_only, get_shard_stats, "get_shard_stats",
    "Get current statistics for a specific shard.",
    {
        /// Shard UID
        pub uid: u32,
    } => |client, input| {
        let handler = redis_enterprise::stats::StatsHandler::new(client);
        let stats = handler
            .shard(input.uid, None)
            .await
            .tool_context("Failed to get shard stats")?;

        CallToolResult::from_serialize(&stats)
    }
);

enterprise_tool!(read_only, get_all_shards_stats, "get_all_shards_stats",
    "Get current statistics for all shards.",
    {} => |client, _input| {
        let handler = redis_enterprise::stats::StatsHandler::new(client);
        let stats = handler
            .shards(None)
            .await
            .tool_context("Failed to get all shards stats")?;

        CallToolResult::from_serialize(&stats)
    }
);

// ============================================================================
// Shard tools
// ============================================================================

enterprise_tool!(read_only, list_shards, "list_shards",
    "List all shards. Optionally filter by database UID.",
    {
        /// Optional database UID to filter by
        #[serde(default)]
        pub database_uid: Option<u32>,
    } => |client, input| {
        let handler = redis_enterprise::shards::ShardHandler::new(client);
        let shards = if let Some(db_uid) = input.database_uid {
            handler
                .list_by_database(db_uid)
                .await
                .tool_context("Failed to list shards")?
        } else {
            handler.list().await.tool_context("Failed to list shards")?
        };

        CallToolResult::from_list("shards", &shards)
    }
);

enterprise_tool!(read_only, get_shard, "get_shard",
    "Get shard details including role (master/replica), status, and assigned node.",
    {
        /// Shard UID (e.g., "1" or "2")
        pub uid: String,
    } => |client, input| {
        let handler = redis_enterprise::shards::ShardHandler::new(client);
        let shard = handler
            .get(&input.uid)
            .await
            .tool_context("Failed to get shard")?;

        CallToolResult::from_serialize(&shard)
    }
);

enterprise_tool!(read_only, list_shards_by_database, "list_shards_by_database",
    "List all shards for a specific database.",
    {
        /// Database UID to list shards for
        pub bdb_uid: u32,
    } => |client, input| {
        let handler = redis_enterprise::shards::ShardHandler::new(client);
        let shards = handler
            .list_by_database(input.bdb_uid)
            .await
            .tool_context("Failed to list shards by database")?;

        CallToolResult::from_list("shards", &shards)
    }
);

enterprise_tool!(read_only, list_shards_by_node, "list_shards_by_node",
    "List all shards on a specific node.",
    {
        /// Node UID to list shards for
        pub node_uid: u32,
    } => |client, input| {
        let handler = redis_enterprise::shards::ShardHandler::new(client);
        let shards = handler
            .list_by_node(input.node_uid)
            .await
            .tool_context("Failed to list shards by node")?;

        CallToolResult::from_list("shards", &shards)
    }
);

// ============================================================================
// Debug Info tools
// ============================================================================

enterprise_tool!(read_only, list_debug_info_tasks, "list_debug_info_tasks",
    "List all debug info collection tasks and their statuses.",
    {} => |client, _input| {
        let handler = DebugInfoHandler::new(client);
        let tasks = handler
            .list()
            .await
            .tool_context("Failed to list debug info tasks")?;

        CallToolResult::from_list("tasks", &tasks)
    }
);

enterprise_tool!(read_only, get_debug_info_status, "get_debug_info_status",
    "Get the status of a debug info collection task by ID.",
    {
        /// The task ID returned when debug info collection was started
        pub task_id: String,
    } => |client, input| {
        let handler = DebugInfoHandler::new(client);
        let status = handler
            .status(&input.task_id)
            .await
            .tool_context("Failed to get debug info status")?;

        CallToolResult::from_serialize(&status)
    }
);

enterprise_tool!(write, create_debug_info, "create_debug_info",
    "Start a debug info collection task. Optionally scope to specific nodes or databases.",
    {
        /// List of node UIDs to collect debug info from (if not specified, collects from all nodes)
        #[serde(default)]
        pub node_uids: Option<Vec<u32>>,
        /// List of database UIDs to collect debug info for (if not specified, collects for all databases)
        #[serde(default)]
        pub bdb_uids: Option<Vec<u32>>,
    } => |client, input| {
        let request = DebugInfoRequest {
            node_uids: input.node_uids,
            bdb_uids: input.bdb_uids,
            include_logs: None,
            include_metrics: None,
            include_configs: None,
            time_range: None,
        };

        let handler = DebugInfoHandler::new(client);
        let status = handler
            .create(request)
            .await
            .tool_context("Failed to create debug info collection")?;

        CallToolResult::from_serialize(&status)
    }
);

// ============================================================================
// Module tools
// ============================================================================

enterprise_tool!(read_only, list_modules, "list_modules",
    "List all installed Redis modules.",
    {} => |client, _input| {
        let handler = redis_enterprise::modules::ModuleHandler::new(client);
        let modules = handler
            .list()
            .await
            .tool_context("Failed to list modules")?;

        CallToolResult::from_list("modules", &modules)
    }
);

enterprise_tool!(read_only, get_module, "get_module",
    "Get details of a specific Redis module by UID.",
    {
        /// Module UID
        pub uid: String,
    } => |client, input| {
        let handler = redis_enterprise::modules::ModuleHandler::new(client);
        let module = handler
            .get(&input.uid)
            .await
            .tool_context("Failed to get module")?;

        CallToolResult::from_serialize(&module)
    }
);