use std::sync::Arc;
use redis_cloud::connectivity::psc::PscEndpointUpdateRequest;
use redis_cloud::connectivity::transit_gateway::TgwAttachmentRequest;
use redis_cloud::connectivity::vpc_peering::VpcPeeringCreateRequest;
use redis_cloud::{
PrincipalType, PrivateLinkAddPrincipalRequest, PrivateLinkCreateRequest, PrivateLinkHandler,
PscHandler, TransitGatewayHandler, VpcPeeringHandler,
};
use schemars::JsonSchema;
use serde::Deserialize;
use tower_mcp::extract::{Json, State};
use tower_mcp::{
CallToolResult, Error as McpError, McpRouter, ResultExt, Tool, ToolBuilder, ToolError,
};
use crate::state::AppState;
#[derive(Debug, Deserialize, JsonSchema)]
pub struct GetVpcPeeringInput {
pub subscription_id: i32,
#[serde(default)]
pub profile: Option<String>,
}
pub fn get_vpc_peering(state: Arc<AppState>) -> Tool {
ToolBuilder::new("get_vpc_peering")
.description("Get VPC peering details for a Redis Cloud subscription.")
.read_only_safe()
.extractor_handler_typed::<_, _, _, GetVpcPeeringInput>(
state,
|State(state): State<Arc<AppState>>, Json(input): Json<GetVpcPeeringInput>| 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 = VpcPeeringHandler::new(client);
let result = handler
.get(input.subscription_id)
.await
.tool_context("Failed to get VPC peering")?;
CallToolResult::from_serialize(&result)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct CreateVpcPeeringInput {
pub subscription_id: i32,
#[serde(default)]
pub provider: Option<String>,
#[serde(default)]
pub vpc_id: Option<String>,
#[serde(default)]
pub aws_region: Option<String>,
#[serde(default)]
pub aws_account_id: Option<String>,
#[serde(default)]
pub vpc_cidr: Option<String>,
#[serde(default)]
pub vpc_cidrs: Option<Vec<String>>,
#[serde(default)]
pub gcp_project_id: Option<String>,
#[serde(default)]
pub network_name: Option<String>,
#[serde(default)]
pub profile: Option<String>,
}
pub fn create_vpc_peering(state: Arc<AppState>) -> Tool {
ToolBuilder::new("create_vpc_peering")
.description(
"Create a VPC peering connection for a Redis Cloud subscription. \
Requires write permission.",
)
.non_destructive()
.extractor_handler_typed::<_, _, _, CreateVpcPeeringInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<CreateVpcPeeringInput>| 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 = VpcPeeringCreateRequest {
provider: input.provider,
vpc_id: input.vpc_id,
aws_region: input.aws_region,
aws_account_id: input.aws_account_id,
vpc_cidr: input.vpc_cidr,
vpc_cidrs: input.vpc_cidrs,
gcp_project_id: input.gcp_project_id,
network_name: input.network_name,
..Default::default()
};
let handler = VpcPeeringHandler::new(client);
let result = handler
.create(input.subscription_id, &request)
.await
.tool_context("Failed to create VPC peering")?;
CallToolResult::from_serialize(&result)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct UpdateVpcPeeringInput {
pub subscription_id: i32,
pub peering_id: i32,
#[serde(default)]
pub provider: Option<String>,
#[serde(default)]
pub vpc_id: Option<String>,
#[serde(default)]
pub aws_region: Option<String>,
#[serde(default)]
pub aws_account_id: Option<String>,
#[serde(default)]
pub vpc_cidr: Option<String>,
#[serde(default)]
pub vpc_cidrs: Option<Vec<String>>,
#[serde(default)]
pub gcp_project_id: Option<String>,
#[serde(default)]
pub network_name: Option<String>,
#[serde(default)]
pub profile: Option<String>,
}
pub fn update_vpc_peering(state: Arc<AppState>) -> Tool {
ToolBuilder::new("update_vpc_peering")
.description(
"Update a VPC peering connection for a Redis Cloud subscription. \
Requires write permission.",
)
.non_destructive()
.extractor_handler_typed::<_, _, _, UpdateVpcPeeringInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<UpdateVpcPeeringInput>| 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 = VpcPeeringCreateRequest {
provider: input.provider,
vpc_id: input.vpc_id,
aws_region: input.aws_region,
aws_account_id: input.aws_account_id,
vpc_cidr: input.vpc_cidr,
vpc_cidrs: input.vpc_cidrs,
gcp_project_id: input.gcp_project_id,
network_name: input.network_name,
..Default::default()
};
let handler = VpcPeeringHandler::new(client);
let result = handler
.update(input.subscription_id, input.peering_id, &request)
.await
.tool_context("Failed to update VPC peering")?;
CallToolResult::from_serialize(&result)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct DeleteVpcPeeringInput {
pub subscription_id: i32,
pub peering_id: i32,
#[serde(default)]
pub profile: Option<String>,
}
pub fn delete_vpc_peering(state: Arc<AppState>) -> Tool {
ToolBuilder::new("delete_vpc_peering")
.description(
"DANGEROUS: Permanently deletes a VPC peering connection. \
Network connectivity will be immediately lost. Requires write permission.",
)
.destructive()
.extractor_handler_typed::<_, _, _, DeleteVpcPeeringInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<DeleteVpcPeeringInput>| 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 = VpcPeeringHandler::new(client);
let result = handler
.delete(input.subscription_id, input.peering_id)
.await
.tool_context("Failed to delete VPC peering")?;
CallToolResult::from_serialize(&result)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct GetAaVpcPeeringInput {
pub subscription_id: i32,
#[serde(default)]
pub profile: Option<String>,
}
pub fn get_aa_vpc_peering(state: Arc<AppState>) -> Tool {
ToolBuilder::new("get_aa_vpc_peering")
.description(
"Get Active-Active VPC peering details for a Redis Cloud subscription.",
)
.read_only_safe()
.extractor_handler_typed::<_, _, _, GetAaVpcPeeringInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<GetAaVpcPeeringInput>| 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 = VpcPeeringHandler::new(client);
let result = handler
.get_active_active(input.subscription_id)
.await
.tool_context("Failed to get AA VPC peering")?;
CallToolResult::from_serialize(&result)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct CreateAaVpcPeeringInput {
pub subscription_id: i32,
#[serde(default)]
pub provider: Option<String>,
#[serde(default)]
pub vpc_id: Option<String>,
#[serde(default)]
pub aws_region: Option<String>,
#[serde(default)]
pub aws_account_id: Option<String>,
#[serde(default)]
pub vpc_cidr: Option<String>,
#[serde(default)]
pub vpc_cidrs: Option<Vec<String>>,
#[serde(default)]
pub gcp_project_id: Option<String>,
#[serde(default)]
pub network_name: Option<String>,
#[serde(default)]
pub profile: Option<String>,
}
pub fn create_aa_vpc_peering(state: Arc<AppState>) -> Tool {
ToolBuilder::new("create_aa_vpc_peering")
.description(
"Create an Active-Active VPC peering connection for a Redis Cloud subscription. \
Requires write permission.",
)
.non_destructive()
.extractor_handler_typed::<_, _, _, CreateAaVpcPeeringInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<CreateAaVpcPeeringInput>| 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 = VpcPeeringCreateRequest {
provider: input.provider,
vpc_id: input.vpc_id,
aws_region: input.aws_region,
aws_account_id: input.aws_account_id,
vpc_cidr: input.vpc_cidr,
vpc_cidrs: input.vpc_cidrs,
gcp_project_id: input.gcp_project_id,
network_name: input.network_name,
..Default::default()
};
let handler = VpcPeeringHandler::new(client);
let result = handler
.create_active_active(input.subscription_id, &request)
.await
.tool_context("Failed to create AA VPC peering")?;
CallToolResult::from_serialize(&result)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct UpdateAaVpcPeeringInput {
pub subscription_id: i32,
pub peering_id: i32,
#[serde(default)]
pub provider: Option<String>,
#[serde(default)]
pub vpc_id: Option<String>,
#[serde(default)]
pub aws_region: Option<String>,
#[serde(default)]
pub aws_account_id: Option<String>,
#[serde(default)]
pub vpc_cidr: Option<String>,
#[serde(default)]
pub vpc_cidrs: Option<Vec<String>>,
#[serde(default)]
pub gcp_project_id: Option<String>,
#[serde(default)]
pub network_name: Option<String>,
#[serde(default)]
pub profile: Option<String>,
}
pub fn update_aa_vpc_peering(state: Arc<AppState>) -> Tool {
ToolBuilder::new("update_aa_vpc_peering")
.description(
"Update an Active-Active VPC peering connection for a Redis Cloud subscription. \
Requires write permission.",
)
.non_destructive()
.extractor_handler_typed::<_, _, _, UpdateAaVpcPeeringInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<UpdateAaVpcPeeringInput>| 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 = VpcPeeringCreateRequest {
provider: input.provider,
vpc_id: input.vpc_id,
aws_region: input.aws_region,
aws_account_id: input.aws_account_id,
vpc_cidr: input.vpc_cidr,
vpc_cidrs: input.vpc_cidrs,
gcp_project_id: input.gcp_project_id,
network_name: input.network_name,
..Default::default()
};
let handler = VpcPeeringHandler::new(client);
let result = handler
.update_active_active(input.subscription_id, input.peering_id, &request)
.await
.tool_context("Failed to update AA VPC peering")?;
CallToolResult::from_serialize(&result)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct DeleteAaVpcPeeringInput {
pub subscription_id: i32,
pub peering_id: i32,
#[serde(default)]
pub profile: Option<String>,
}
pub fn delete_aa_vpc_peering(state: Arc<AppState>) -> Tool {
ToolBuilder::new("delete_aa_vpc_peering")
.description(
"DANGEROUS: Permanently deletes an Active-Active VPC peering connection. \
Network connectivity will be immediately lost. Requires write permission.",
)
.destructive()
.extractor_handler_typed::<_, _, _, DeleteAaVpcPeeringInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<DeleteAaVpcPeeringInput>| 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 = VpcPeeringHandler::new(client);
let result = handler
.delete_active_active(input.subscription_id, input.peering_id)
.await
.tool_context("Failed to delete AA VPC peering")?;
CallToolResult::from_serialize(&result)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct GetTgwAttachmentsInput {
pub subscription_id: i32,
#[serde(default)]
pub profile: Option<String>,
}
pub fn get_tgw_attachments(state: Arc<AppState>) -> Tool {
ToolBuilder::new("get_tgw_attachments")
.description(
"Get Transit Gateway attachments for a Redis Cloud subscription.",
)
.read_only_safe()
.extractor_handler_typed::<_, _, _, GetTgwAttachmentsInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<GetTgwAttachmentsInput>| 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 = TransitGatewayHandler::new(client);
let result = handler
.get_attachments(input.subscription_id)
.await
.tool_context("Failed to get TGW attachments")?;
CallToolResult::from_serialize(&result)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct GetTgwInvitationsInput {
pub subscription_id: i32,
#[serde(default)]
pub profile: Option<String>,
}
pub fn get_tgw_invitations(state: Arc<AppState>) -> Tool {
ToolBuilder::new("get_tgw_invitations")
.description(
"Get Transit Gateway shared invitations for a Redis Cloud subscription.",
)
.read_only_safe()
.extractor_handler_typed::<_, _, _, GetTgwInvitationsInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<GetTgwInvitationsInput>| 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 = TransitGatewayHandler::new(client);
let result = handler
.get_shared_invitations(input.subscription_id)
.await
.tool_context("Failed to get TGW invitations")?;
CallToolResult::from_serialize(&result)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct AcceptTgwInvitationInput {
pub subscription_id: i32,
pub invitation_id: String,
#[serde(default)]
pub profile: Option<String>,
}
pub fn accept_tgw_invitation(state: Arc<AppState>) -> Tool {
ToolBuilder::new("accept_tgw_invitation")
.description(
"Accept a Transit Gateway resource share invitation. \
Requires write permission.",
)
.non_destructive()
.extractor_handler_typed::<_, _, _, AcceptTgwInvitationInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<AcceptTgwInvitationInput>| 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 = TransitGatewayHandler::new(client);
let result = handler
.accept_resource_share(input.subscription_id, input.invitation_id)
.await
.tool_context("Failed to accept TGW invitation")?;
CallToolResult::from_serialize(&result)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct RejectTgwInvitationInput {
pub subscription_id: i32,
pub invitation_id: String,
#[serde(default)]
pub profile: Option<String>,
}
pub fn reject_tgw_invitation(state: Arc<AppState>) -> Tool {
ToolBuilder::new("reject_tgw_invitation")
.description(
"Reject a Transit Gateway resource share invitation. \
Requires write permission.",
)
.non_destructive()
.extractor_handler_typed::<_, _, _, RejectTgwInvitationInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<RejectTgwInvitationInput>| 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 = TransitGatewayHandler::new(client);
let result = handler
.reject_resource_share(input.subscription_id, input.invitation_id)
.await
.tool_context("Failed to reject TGW invitation")?;
CallToolResult::from_serialize(&result)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct CreateTgwAttachmentInput {
pub subscription_id: i32,
#[serde(default)]
pub aws_account_id: Option<String>,
#[serde(default)]
pub tgw_id: Option<String>,
#[serde(default)]
pub cidrs: Option<Vec<String>>,
#[serde(default)]
pub profile: Option<String>,
}
pub fn create_tgw_attachment(state: Arc<AppState>) -> Tool {
ToolBuilder::new("create_tgw_attachment")
.description(
"Create a Transit Gateway attachment for a Redis Cloud subscription. \
Requires write permission.",
)
.non_destructive()
.extractor_handler_typed::<_, _, _, CreateTgwAttachmentInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<CreateTgwAttachmentInput>| 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 = TgwAttachmentRequest {
aws_account_id: input.aws_account_id,
tgw_id: input.tgw_id,
cidrs: input.cidrs,
};
let handler = TransitGatewayHandler::new(client);
let result = handler
.create_attachment(input.subscription_id, &request)
.await
.tool_context("Failed to create TGW attachment")?;
CallToolResult::from_serialize(&result)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct UpdateTgwAttachmentCidrsInput {
pub subscription_id: i32,
pub attachment_id: String,
#[serde(default)]
pub aws_account_id: Option<String>,
#[serde(default)]
pub tgw_id: Option<String>,
#[serde(default)]
pub cidrs: Option<Vec<String>>,
#[serde(default)]
pub profile: Option<String>,
}
pub fn update_tgw_attachment_cidrs(state: Arc<AppState>) -> Tool {
ToolBuilder::new("update_tgw_attachment_cidrs")
.description(
"Update CIDRs for a Transit Gateway attachment. \
Requires write permission.",
)
.non_destructive()
.extractor_handler_typed::<_, _, _, UpdateTgwAttachmentCidrsInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<UpdateTgwAttachmentCidrsInput>| 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 = TgwAttachmentRequest {
aws_account_id: input.aws_account_id,
tgw_id: input.tgw_id,
cidrs: input.cidrs,
};
let handler = TransitGatewayHandler::new(client);
let result = handler
.update_attachment_cidrs(input.subscription_id, input.attachment_id, &request)
.await
.tool_context("Failed to update TGW attachment CIDRs")?;
CallToolResult::from_serialize(&result)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct DeleteTgwAttachmentInput {
pub subscription_id: i32,
pub attachment_id: String,
#[serde(default)]
pub profile: Option<String>,
}
pub fn delete_tgw_attachment(state: Arc<AppState>) -> Tool {
ToolBuilder::new("delete_tgw_attachment")
.description(
"DANGEROUS: Permanently deletes a Transit Gateway attachment. \
Network connectivity will be immediately lost. Requires write permission.",
)
.destructive()
.extractor_handler_typed::<_, _, _, DeleteTgwAttachmentInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<DeleteTgwAttachmentInput>| 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 = TransitGatewayHandler::new(client);
let result = handler
.delete_attachment(input.subscription_id, input.attachment_id)
.await
.tool_context("Failed to delete TGW attachment")?;
CallToolResult::from_serialize(&result)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct GetAaTgwAttachmentsInput {
pub subscription_id: i32,
#[serde(default)]
pub profile: Option<String>,
}
pub fn get_aa_tgw_attachments(state: Arc<AppState>) -> Tool {
ToolBuilder::new("get_aa_tgw_attachments")
.description(
"Get Active-Active Transit Gateway attachments for a Redis Cloud subscription.",
)
.read_only_safe()
.extractor_handler_typed::<_, _, _, GetAaTgwAttachmentsInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<GetAaTgwAttachmentsInput>| 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 = TransitGatewayHandler::new(client);
let result = handler
.get_attachments_active_active(input.subscription_id)
.await
.tool_context("Failed to get AA TGW attachments")?;
CallToolResult::from_serialize(&result)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct GetAaTgwInvitationsInput {
pub subscription_id: i32,
#[serde(default)]
pub profile: Option<String>,
}
pub fn get_aa_tgw_invitations(state: Arc<AppState>) -> Tool {
ToolBuilder::new("get_aa_tgw_invitations")
.description(
"Get Active-Active Transit Gateway shared invitations for a Redis Cloud subscription.",
)
.read_only_safe()
.extractor_handler_typed::<_, _, _, GetAaTgwInvitationsInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<GetAaTgwInvitationsInput>| 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 = TransitGatewayHandler::new(client);
let result = handler
.get_shared_invitations_active_active(input.subscription_id)
.await
.tool_context("Failed to get AA TGW invitations")?;
CallToolResult::from_serialize(&result)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct AcceptAaTgwInvitationInput {
pub subscription_id: i32,
pub region_id: i32,
pub invitation_id: String,
#[serde(default)]
pub profile: Option<String>,
}
pub fn accept_aa_tgw_invitation(state: Arc<AppState>) -> Tool {
ToolBuilder::new("accept_aa_tgw_invitation")
.description(
"Accept an Active-Active Transit Gateway resource share invitation. \
Requires write permission.",
)
.non_destructive()
.extractor_handler_typed::<_, _, _, AcceptAaTgwInvitationInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<AcceptAaTgwInvitationInput>| 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 = TransitGatewayHandler::new(client);
let result = handler
.accept_resource_share_active_active(
input.subscription_id,
input.region_id,
input.invitation_id,
)
.await
.tool_context("Failed to accept AA TGW invitation")?;
CallToolResult::from_serialize(&result)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct RejectAaTgwInvitationInput {
pub subscription_id: i32,
pub region_id: i32,
pub invitation_id: String,
#[serde(default)]
pub profile: Option<String>,
}
pub fn reject_aa_tgw_invitation(state: Arc<AppState>) -> Tool {
ToolBuilder::new("reject_aa_tgw_invitation")
.description(
"Reject an Active-Active Transit Gateway resource share invitation. \
Requires write permission.",
)
.non_destructive()
.extractor_handler_typed::<_, _, _, RejectAaTgwInvitationInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<RejectAaTgwInvitationInput>| 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 = TransitGatewayHandler::new(client);
let result = handler
.reject_resource_share_active_active(
input.subscription_id,
input.region_id,
input.invitation_id,
)
.await
.tool_context("Failed to reject AA TGW invitation")?;
CallToolResult::from_serialize(&result)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct CreateAaTgwAttachmentInput {
pub subscription_id: i32,
pub region_id: i32,
#[serde(default)]
pub aws_account_id: Option<String>,
#[serde(default)]
pub tgw_id: Option<String>,
#[serde(default)]
pub cidrs: Option<Vec<String>>,
#[serde(default)]
pub profile: Option<String>,
}
pub fn create_aa_tgw_attachment(state: Arc<AppState>) -> Tool {
ToolBuilder::new("create_aa_tgw_attachment")
.description(
"Create an Active-Active Transit Gateway attachment. \
Requires write permission.",
)
.non_destructive()
.extractor_handler_typed::<_, _, _, CreateAaTgwAttachmentInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<CreateAaTgwAttachmentInput>| 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 = TgwAttachmentRequest {
aws_account_id: input.aws_account_id,
tgw_id: input.tgw_id,
cidrs: input.cidrs,
};
let handler = TransitGatewayHandler::new(client);
let result = handler
.create_attachment_active_active(
input.subscription_id,
input.region_id,
&request,
)
.await
.tool_context("Failed to create AA TGW attachment")?;
CallToolResult::from_serialize(&result)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct UpdateAaTgwAttachmentCidrsInput {
pub subscription_id: i32,
pub region_id: i32,
pub attachment_id: String,
#[serde(default)]
pub aws_account_id: Option<String>,
#[serde(default)]
pub tgw_id: Option<String>,
#[serde(default)]
pub cidrs: Option<Vec<String>>,
#[serde(default)]
pub profile: Option<String>,
}
pub fn update_aa_tgw_attachment_cidrs(state: Arc<AppState>) -> Tool {
ToolBuilder::new("update_aa_tgw_attachment_cidrs")
.description(
"Update CIDRs for an Active-Active Transit Gateway attachment. \
Requires write permission.",
)
.non_destructive()
.extractor_handler_typed::<_, _, _, UpdateAaTgwAttachmentCidrsInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<UpdateAaTgwAttachmentCidrsInput>| 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 = TgwAttachmentRequest {
aws_account_id: input.aws_account_id,
tgw_id: input.tgw_id,
cidrs: input.cidrs,
};
let handler = TransitGatewayHandler::new(client);
let result = handler
.update_attachment_cidrs_active_active(
input.subscription_id,
input.region_id,
input.attachment_id,
&request,
)
.await
.tool_context("Failed to update AA TGW attachment CIDRs")?;
CallToolResult::from_serialize(&result)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct DeleteAaTgwAttachmentInput {
pub subscription_id: i32,
pub region_id: i32,
pub attachment_id: String,
#[serde(default)]
pub profile: Option<String>,
}
pub fn delete_aa_tgw_attachment(state: Arc<AppState>) -> Tool {
ToolBuilder::new("delete_aa_tgw_attachment")
.description(
"DANGEROUS: Permanently deletes an Active-Active Transit Gateway attachment. \
Network connectivity will be immediately lost. Requires write permission.",
)
.destructive()
.extractor_handler_typed::<_, _, _, DeleteAaTgwAttachmentInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<DeleteAaTgwAttachmentInput>| 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 = TransitGatewayHandler::new(client);
let result = handler
.delete_attachment_active_active(
input.subscription_id,
input.region_id,
input.attachment_id,
)
.await
.tool_context("Failed to delete AA TGW attachment")?;
CallToolResult::from_serialize(&result)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct GetPscServiceInput {
pub subscription_id: i32,
#[serde(default)]
pub profile: Option<String>,
}
pub fn get_psc_service(state: Arc<AppState>) -> Tool {
ToolBuilder::new("get_psc_service")
.description("Get Private Service Connect service for a Redis Cloud subscription.")
.read_only_safe()
.extractor_handler_typed::<_, _, _, GetPscServiceInput>(
state,
|State(state): State<Arc<AppState>>, Json(input): Json<GetPscServiceInput>| 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 = PscHandler::new(client);
let result = handler
.get_service(input.subscription_id)
.await
.tool_context("Failed to get PSC service")?;
CallToolResult::from_serialize(&result)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct CreatePscServiceInput {
pub subscription_id: i32,
#[serde(default)]
pub profile: Option<String>,
}
pub fn create_psc_service(state: Arc<AppState>) -> Tool {
ToolBuilder::new("create_psc_service")
.description(
"Create a Private Service Connect service for a Redis Cloud subscription. \
Requires write permission.",
)
.non_destructive()
.extractor_handler_typed::<_, _, _, CreatePscServiceInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<CreatePscServiceInput>| 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 = PscHandler::new(client);
let result = handler
.create_service(input.subscription_id)
.await
.tool_context("Failed to create PSC service")?;
CallToolResult::from_serialize(&result)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct DeletePscServiceInput {
pub subscription_id: i32,
#[serde(default)]
pub profile: Option<String>,
}
pub fn delete_psc_service(state: Arc<AppState>) -> Tool {
ToolBuilder::new("delete_psc_service")
.description(
"DANGEROUS: Permanently deletes a Private Service Connect service. \
All endpoints will be disconnected. Requires write permission.",
)
.destructive()
.extractor_handler_typed::<_, _, _, DeletePscServiceInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<DeletePscServiceInput>| 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 = PscHandler::new(client);
let result = handler
.delete_service(input.subscription_id)
.await
.tool_context("Failed to delete PSC service")?;
CallToolResult::from_serialize(&result)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct GetPscEndpointsInput {
pub subscription_id: i32,
#[serde(default)]
pub profile: Option<String>,
}
pub fn get_psc_endpoints(state: Arc<AppState>) -> Tool {
ToolBuilder::new("get_psc_endpoints")
.description(
"Get Private Service Connect endpoints for a Redis Cloud subscription.",
)
.read_only_safe()
.extractor_handler_typed::<_, _, _, GetPscEndpointsInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<GetPscEndpointsInput>| 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 = PscHandler::new(client);
let result = handler
.get_endpoints(input.subscription_id)
.await
.tool_context("Failed to get PSC endpoints")?;
CallToolResult::from_serialize(&result)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct CreatePscEndpointInput {
pub subscription_id: i32,
pub psc_service_id: i32,
pub endpoint_id: i32,
#[serde(default)]
pub gcp_project_id: Option<String>,
#[serde(default)]
pub gcp_vpc_name: Option<String>,
#[serde(default)]
pub gcp_vpc_subnet_name: Option<String>,
#[serde(default)]
pub endpoint_connection_name: Option<String>,
#[serde(default)]
pub profile: Option<String>,
}
pub fn create_psc_endpoint(state: Arc<AppState>) -> Tool {
ToolBuilder::new("create_psc_endpoint")
.description(
"Create a Private Service Connect endpoint. \
Requires write permission.",
)
.non_destructive()
.extractor_handler_typed::<_, _, _, CreatePscEndpointInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<CreatePscEndpointInput>| 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 = PscEndpointUpdateRequest {
subscription_id: input.subscription_id,
psc_service_id: input.psc_service_id,
endpoint_id: input.endpoint_id,
gcp_project_id: input.gcp_project_id,
gcp_vpc_name: input.gcp_vpc_name,
gcp_vpc_subnet_name: input.gcp_vpc_subnet_name,
endpoint_connection_name: input.endpoint_connection_name,
};
let handler = PscHandler::new(client);
let result = handler
.create_endpoint(input.subscription_id, &request)
.await
.tool_context("Failed to create PSC endpoint")?;
CallToolResult::from_serialize(&result)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct UpdatePscEndpointInput {
pub subscription_id: i32,
pub endpoint_id: i32,
pub psc_service_id: i32,
#[serde(default)]
pub gcp_project_id: Option<String>,
#[serde(default)]
pub gcp_vpc_name: Option<String>,
#[serde(default)]
pub gcp_vpc_subnet_name: Option<String>,
#[serde(default)]
pub endpoint_connection_name: Option<String>,
#[serde(default)]
pub profile: Option<String>,
}
pub fn update_psc_endpoint(state: Arc<AppState>) -> Tool {
ToolBuilder::new("update_psc_endpoint")
.description(
"Update a Private Service Connect endpoint. \
Requires write permission.",
)
.non_destructive()
.extractor_handler_typed::<_, _, _, UpdatePscEndpointInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<UpdatePscEndpointInput>| 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 = PscEndpointUpdateRequest {
subscription_id: input.subscription_id,
psc_service_id: input.psc_service_id,
endpoint_id: input.endpoint_id,
gcp_project_id: input.gcp_project_id,
gcp_vpc_name: input.gcp_vpc_name,
gcp_vpc_subnet_name: input.gcp_vpc_subnet_name,
endpoint_connection_name: input.endpoint_connection_name,
};
let handler = PscHandler::new(client);
let result = handler
.update_endpoint(input.subscription_id, input.endpoint_id, &request)
.await
.tool_context("Failed to update PSC endpoint")?;
CallToolResult::from_serialize(&result)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct DeletePscEndpointInput {
pub subscription_id: i32,
pub endpoint_id: i32,
#[serde(default)]
pub profile: Option<String>,
}
pub fn delete_psc_endpoint(state: Arc<AppState>) -> Tool {
ToolBuilder::new("delete_psc_endpoint")
.description(
"DANGEROUS: Permanently deletes a Private Service Connect endpoint. \
Connectivity will be immediately lost. Requires write permission.",
)
.destructive()
.extractor_handler_typed::<_, _, _, DeletePscEndpointInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<DeletePscEndpointInput>| 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 = PscHandler::new(client);
let result = handler
.delete_endpoint(input.subscription_id, input.endpoint_id)
.await
.tool_context("Failed to delete PSC endpoint")?;
CallToolResult::from_serialize(&result)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct GetPscCreationScriptInput {
pub subscription_id: i32,
pub endpoint_id: i32,
#[serde(default)]
pub profile: Option<String>,
}
pub fn get_psc_creation_script(state: Arc<AppState>) -> Tool {
ToolBuilder::new("get_psc_creation_script")
.description(
"Get the creation script for a Private Service Connect endpoint.",
)
.read_only_safe()
.extractor_handler_typed::<_, _, _, GetPscCreationScriptInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<GetPscCreationScriptInput>| 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 = PscHandler::new(client);
let result = handler
.get_endpoint_creation_script(input.subscription_id, input.endpoint_id)
.await
.tool_context("Failed to get PSC creation script")?;
Ok(CallToolResult::text(result))
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct GetPscDeletionScriptInput {
pub subscription_id: i32,
pub endpoint_id: i32,
#[serde(default)]
pub profile: Option<String>,
}
pub fn get_psc_deletion_script(state: Arc<AppState>) -> Tool {
ToolBuilder::new("get_psc_deletion_script")
.description(
"Get the deletion script for a Private Service Connect endpoint.",
)
.read_only_safe()
.extractor_handler_typed::<_, _, _, GetPscDeletionScriptInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<GetPscDeletionScriptInput>| 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 = PscHandler::new(client);
let result = handler
.get_endpoint_deletion_script(input.subscription_id, input.endpoint_id)
.await
.tool_context("Failed to get PSC deletion script")?;
Ok(CallToolResult::text(result))
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct GetAaPscServiceInput {
pub subscription_id: i32,
#[serde(default)]
pub profile: Option<String>,
}
pub fn get_aa_psc_service(state: Arc<AppState>) -> Tool {
ToolBuilder::new("get_aa_psc_service")
.description(
"Get Active-Active Private Service Connect service for a Redis Cloud subscription.",
)
.read_only_safe()
.extractor_handler_typed::<_, _, _, GetAaPscServiceInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<GetAaPscServiceInput>| 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 = PscHandler::new(client);
let result = handler
.get_service_active_active(input.subscription_id)
.await
.tool_context("Failed to get AA PSC service")?;
CallToolResult::from_serialize(&result)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct CreateAaPscServiceInput {
pub subscription_id: i32,
#[serde(default)]
pub profile: Option<String>,
}
pub fn create_aa_psc_service(state: Arc<AppState>) -> Tool {
ToolBuilder::new("create_aa_psc_service")
.description(
"Create an Active-Active Private Service Connect service. \
Requires write permission.",
)
.non_destructive()
.extractor_handler_typed::<_, _, _, CreateAaPscServiceInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<CreateAaPscServiceInput>| 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 = PscHandler::new(client);
let result = handler
.create_service_active_active(input.subscription_id)
.await
.tool_context("Failed to create AA PSC service")?;
CallToolResult::from_serialize(&result)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct DeleteAaPscServiceInput {
pub subscription_id: i32,
#[serde(default)]
pub profile: Option<String>,
}
pub fn delete_aa_psc_service(state: Arc<AppState>) -> Tool {
ToolBuilder::new("delete_aa_psc_service")
.description(
"DANGEROUS: Permanently deletes an Active-Active Private Service Connect service. \
All endpoints will be disconnected. Requires write permission.",
)
.destructive()
.extractor_handler_typed::<_, _, _, DeleteAaPscServiceInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<DeleteAaPscServiceInput>| 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 = PscHandler::new(client);
let result = handler
.delete_service_active_active(input.subscription_id)
.await
.tool_context("Failed to delete AA PSC service")?;
CallToolResult::from_serialize(&result)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct GetAaPscEndpointsInput {
pub subscription_id: i32,
#[serde(default)]
pub profile: Option<String>,
}
pub fn get_aa_psc_endpoints(state: Arc<AppState>) -> Tool {
ToolBuilder::new("get_aa_psc_endpoints")
.description(
"Get Active-Active Private Service Connect endpoints for a Redis Cloud subscription.",
)
.read_only_safe()
.extractor_handler_typed::<_, _, _, GetAaPscEndpointsInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<GetAaPscEndpointsInput>| 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 = PscHandler::new(client);
let result = handler
.get_endpoints_active_active(input.subscription_id)
.await
.tool_context("Failed to get AA PSC endpoints")?;
CallToolResult::from_serialize(&result)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct CreateAaPscEndpointInput {
pub subscription_id: i32,
pub psc_service_id: i32,
pub endpoint_id: i32,
#[serde(default)]
pub gcp_project_id: Option<String>,
#[serde(default)]
pub gcp_vpc_name: Option<String>,
#[serde(default)]
pub gcp_vpc_subnet_name: Option<String>,
#[serde(default)]
pub endpoint_connection_name: Option<String>,
#[serde(default)]
pub profile: Option<String>,
}
pub fn create_aa_psc_endpoint(state: Arc<AppState>) -> Tool {
ToolBuilder::new("create_aa_psc_endpoint")
.description(
"Create an Active-Active Private Service Connect endpoint. \
Requires write permission.",
)
.non_destructive()
.extractor_handler_typed::<_, _, _, CreateAaPscEndpointInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<CreateAaPscEndpointInput>| 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 = PscEndpointUpdateRequest {
subscription_id: input.subscription_id,
psc_service_id: input.psc_service_id,
endpoint_id: input.endpoint_id,
gcp_project_id: input.gcp_project_id,
gcp_vpc_name: input.gcp_vpc_name,
gcp_vpc_subnet_name: input.gcp_vpc_subnet_name,
endpoint_connection_name: input.endpoint_connection_name,
};
let handler = PscHandler::new(client);
let result = handler
.create_endpoint_active_active(input.subscription_id, &request)
.await
.tool_context("Failed to create AA PSC endpoint")?;
CallToolResult::from_serialize(&result)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct UpdateAaPscEndpointInput {
pub subscription_id: i32,
pub region_id: i32,
pub endpoint_id: i32,
pub psc_service_id: i32,
#[serde(default)]
pub gcp_project_id: Option<String>,
#[serde(default)]
pub gcp_vpc_name: Option<String>,
#[serde(default)]
pub gcp_vpc_subnet_name: Option<String>,
#[serde(default)]
pub endpoint_connection_name: Option<String>,
#[serde(default)]
pub profile: Option<String>,
}
pub fn update_aa_psc_endpoint(state: Arc<AppState>) -> Tool {
ToolBuilder::new("update_aa_psc_endpoint")
.description(
"Update an Active-Active Private Service Connect endpoint. \
Requires write permission.",
)
.non_destructive()
.extractor_handler_typed::<_, _, _, UpdateAaPscEndpointInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<UpdateAaPscEndpointInput>| 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 = PscEndpointUpdateRequest {
subscription_id: input.subscription_id,
psc_service_id: input.psc_service_id,
endpoint_id: input.endpoint_id,
gcp_project_id: input.gcp_project_id,
gcp_vpc_name: input.gcp_vpc_name,
gcp_vpc_subnet_name: input.gcp_vpc_subnet_name,
endpoint_connection_name: input.endpoint_connection_name,
};
let handler = PscHandler::new(client);
let result = handler
.update_endpoint_active_active(
input.subscription_id,
input.region_id,
input.endpoint_id,
&request,
)
.await
.tool_context("Failed to update AA PSC endpoint")?;
CallToolResult::from_serialize(&result)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct DeleteAaPscEndpointInput {
pub subscription_id: i32,
pub region_id: i32,
pub endpoint_id: i32,
#[serde(default)]
pub profile: Option<String>,
}
pub fn delete_aa_psc_endpoint(state: Arc<AppState>) -> Tool {
ToolBuilder::new("delete_aa_psc_endpoint")
.description(
"DANGEROUS: Permanently deletes an Active-Active Private Service Connect endpoint. \
Connectivity will be immediately lost. Requires write permission.",
)
.destructive()
.extractor_handler_typed::<_, _, _, DeleteAaPscEndpointInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<DeleteAaPscEndpointInput>| 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 = PscHandler::new(client);
let result = handler
.delete_endpoint_active_active(
input.subscription_id,
input.region_id,
input.endpoint_id,
)
.await
.tool_context("Failed to delete AA PSC endpoint")?;
CallToolResult::from_serialize(&result)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct GetAaPscCreationScriptInput {
pub subscription_id: i32,
pub region_id: i32,
pub psc_service_id: i32,
pub endpoint_id: i32,
#[serde(default)]
pub profile: Option<String>,
}
pub fn get_aa_psc_creation_script(state: Arc<AppState>) -> Tool {
ToolBuilder::new("get_aa_psc_creation_script")
.description(
"Get the creation script for an Active-Active Private Service Connect endpoint.",
)
.read_only_safe()
.extractor_handler_typed::<_, _, _, GetAaPscCreationScriptInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<GetAaPscCreationScriptInput>| 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 = PscHandler::new(client);
let result = handler
.get_endpoint_creation_script_active_active(
input.subscription_id,
input.region_id,
input.psc_service_id,
input.endpoint_id,
)
.await
.tool_context("Failed to get AA PSC creation script")?;
Ok(CallToolResult::text(result))
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct GetAaPscDeletionScriptInput {
pub subscription_id: i32,
pub region_id: i32,
pub psc_service_id: i32,
pub endpoint_id: i32,
#[serde(default)]
pub profile: Option<String>,
}
pub fn get_aa_psc_deletion_script(state: Arc<AppState>) -> Tool {
ToolBuilder::new("get_aa_psc_deletion_script")
.description(
"Get the deletion script for an Active-Active Private Service Connect endpoint.",
)
.read_only_safe()
.extractor_handler_typed::<_, _, _, GetAaPscDeletionScriptInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<GetAaPscDeletionScriptInput>| 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 = PscHandler::new(client);
let result = handler
.get_endpoint_deletion_script_active_active(
input.subscription_id,
input.region_id,
input.psc_service_id,
input.endpoint_id,
)
.await
.tool_context("Failed to get AA PSC deletion script")?;
Ok(CallToolResult::text(result))
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct GetPrivateLinkInput {
pub subscription_id: i32,
#[serde(default)]
pub profile: Option<String>,
}
pub fn get_private_link(state: Arc<AppState>) -> Tool {
ToolBuilder::new("get_private_link")
.description(
"Get AWS PrivateLink configuration for a Redis Cloud subscription.",
)
.read_only_safe()
.extractor_handler_typed::<_, _, _, GetPrivateLinkInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<GetPrivateLinkInput>| 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 = PrivateLinkHandler::new(client);
let result = handler
.get(input.subscription_id)
.await
.tool_context("Failed to get PrivateLink")?;
CallToolResult::from_serialize(&result)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct CreatePrivateLinkInput {
pub subscription_id: i32,
pub share_name: String,
pub principal: String,
pub principal_type: String,
#[serde(default)]
pub alias: Option<String>,
#[serde(default)]
pub profile: Option<String>,
}
fn parse_principal_type(s: &str) -> Result<PrincipalType, ToolError> {
match s.to_lowercase().as_str() {
"aws_account" | "awsaccount" => Ok(PrincipalType::AwsAccount),
"organization" => Ok(PrincipalType::Organization),
"organization_unit" | "organizationunit" => Ok(PrincipalType::OrganizationUnit),
"iam_role" | "iamrole" => Ok(PrincipalType::IamRole),
"iam_user" | "iamuser" => Ok(PrincipalType::IamUser),
"service_principal" | "serviceprincipal" => Ok(PrincipalType::ServicePrincipal),
_ => Err(ToolError::new(format!(
"Invalid principal type: {}. Expected one of: aws_account, organization, organization_unit, iam_role, iam_user, service_principal",
s
))),
}
}
pub fn create_private_link(state: Arc<AppState>) -> Tool {
ToolBuilder::new("create_private_link")
.description(
"Create an AWS PrivateLink configuration for a Redis Cloud subscription. \
Requires write permission.",
)
.non_destructive()
.extractor_handler_typed::<_, _, _, CreatePrivateLinkInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<CreatePrivateLinkInput>| 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 principal_type = parse_principal_type(&input.principal_type)?;
let request = PrivateLinkCreateRequest {
share_name: input.share_name,
principal: input.principal,
principal_type,
alias: input.alias,
};
let handler = PrivateLinkHandler::new(client);
let result = handler
.create(input.subscription_id, &request)
.await
.tool_context("Failed to create PrivateLink")?;
CallToolResult::from_serialize(&result)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct DeletePrivateLinkInput {
pub subscription_id: i32,
#[serde(default)]
pub profile: Option<String>,
}
pub fn delete_private_link(state: Arc<AppState>) -> Tool {
ToolBuilder::new("delete_private_link")
.description(
"DANGEROUS: Permanently deletes an AWS PrivateLink configuration. \
Connectivity will be immediately lost. Requires write permission.",
)
.destructive()
.extractor_handler_typed::<_, _, _, DeletePrivateLinkInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<DeletePrivateLinkInput>| 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 = PrivateLinkHandler::new(client);
let result = handler
.delete(input.subscription_id)
.await
.tool_context("Failed to delete PrivateLink")?;
CallToolResult::from_serialize(&result)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct AddPrivateLinkPrincipalsInput {
pub subscription_id: i32,
pub principal: String,
#[serde(default)]
pub principal_type: Option<String>,
#[serde(default)]
pub alias: Option<String>,
#[serde(default)]
pub profile: Option<String>,
}
pub fn add_private_link_principals(state: Arc<AppState>) -> Tool {
ToolBuilder::new("add_private_link_principals")
.description(
"Add AWS principals to a PrivateLink access list. \
Requires write permission.",
)
.non_destructive()
.extractor_handler_typed::<_, _, _, AddPrivateLinkPrincipalsInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<AddPrivateLinkPrincipalsInput>| 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 principal_type = input
.principal_type
.map(|pt| parse_principal_type(&pt))
.transpose()?;
let request = PrivateLinkAddPrincipalRequest {
principal: input.principal,
principal_type,
alias: input.alias,
};
let handler = PrivateLinkHandler::new(client);
let result = handler
.add_principals(input.subscription_id, &request)
.await
.tool_context("Failed to add PrivateLink principals")?;
CallToolResult::from_serialize(&result)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct RemovePrivateLinkPrincipalsInput {
pub subscription_id: i32,
pub principal: String,
#[serde(default)]
pub principal_type: Option<String>,
#[serde(default)]
pub alias: Option<String>,
#[serde(default)]
pub profile: Option<String>,
}
pub fn remove_private_link_principals(state: Arc<AppState>) -> Tool {
ToolBuilder::new("remove_private_link_principals")
.description(
"Remove AWS principals from a PrivateLink access list. \
Requires write permission.",
)
.non_destructive()
.extractor_handler_typed::<_, _, _, RemovePrivateLinkPrincipalsInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<RemovePrivateLinkPrincipalsInput>| 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 principal_type = input
.principal_type
.map(|pt| parse_principal_type(&pt))
.transpose()?;
let request =
redis_cloud::connectivity::private_link::PrivateLinkRemovePrincipalRequest {
principal: input.principal,
principal_type,
alias: input.alias,
};
let handler = PrivateLinkHandler::new(client);
let result = handler
.remove_principals(input.subscription_id, &request)
.await
.tool_context("Failed to remove PrivateLink principals")?;
CallToolResult::from_serialize(&result)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct GetPrivateLinkEndpointScriptInput {
pub subscription_id: i32,
#[serde(default)]
pub profile: Option<String>,
}
pub fn get_private_link_endpoint_script(state: Arc<AppState>) -> Tool {
ToolBuilder::new("get_private_link_endpoint_script")
.description("Get the endpoint creation script for an AWS PrivateLink configuration.")
.read_only_safe()
.extractor_handler_typed::<_, _, _, GetPrivateLinkEndpointScriptInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<GetPrivateLinkEndpointScriptInput>| 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 = PrivateLinkHandler::new(client);
let result = handler
.get_endpoint_script(input.subscription_id)
.await
.tool_context("Failed to get PrivateLink endpoint script")?;
CallToolResult::from_serialize(&result)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct GetAaPrivateLinkInput {
pub subscription_id: i32,
pub region_id: i32,
#[serde(default)]
pub profile: Option<String>,
}
pub fn get_aa_private_link(state: Arc<AppState>) -> Tool {
ToolBuilder::new("get_aa_private_link")
.description(
"Get Active-Active AWS PrivateLink configuration for a Redis Cloud subscription region.",
)
.read_only_safe()
.extractor_handler_typed::<_, _, _, GetAaPrivateLinkInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<GetAaPrivateLinkInput>| 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 = PrivateLinkHandler::new(client);
let result = handler
.get_active_active(input.subscription_id, input.region_id)
.await
.tool_context("Failed to get AA PrivateLink")?;
CallToolResult::from_serialize(&result)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct CreateAaPrivateLinkInput {
pub subscription_id: i32,
pub region_id: i32,
pub share_name: String,
pub principal: String,
pub principal_type: String,
#[serde(default)]
pub alias: Option<String>,
#[serde(default)]
pub profile: Option<String>,
}
pub fn create_aa_private_link(state: Arc<AppState>) -> Tool {
ToolBuilder::new("create_aa_private_link")
.description(
"Create an Active-Active AWS PrivateLink configuration. \
Requires write permission.",
)
.non_destructive()
.extractor_handler_typed::<_, _, _, CreateAaPrivateLinkInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<CreateAaPrivateLinkInput>| 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 principal_type = parse_principal_type(&input.principal_type)?;
let request = PrivateLinkCreateRequest {
share_name: input.share_name,
principal: input.principal,
principal_type,
alias: input.alias,
};
let handler = PrivateLinkHandler::new(client);
let result = handler
.create_active_active(input.subscription_id, input.region_id, &request)
.await
.tool_context("Failed to create AA PrivateLink")?;
CallToolResult::from_serialize(&result)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct AddAaPrivateLinkPrincipalsInput {
pub subscription_id: i32,
pub region_id: i32,
pub principal: String,
#[serde(default)]
pub principal_type: Option<String>,
#[serde(default)]
pub alias: Option<String>,
#[serde(default)]
pub profile: Option<String>,
}
pub fn add_aa_private_link_principals(state: Arc<AppState>) -> Tool {
ToolBuilder::new("add_aa_private_link_principals")
.description(
"Add AWS principals to an Active-Active PrivateLink access list. \
Requires write permission.",
)
.non_destructive()
.extractor_handler_typed::<_, _, _, AddAaPrivateLinkPrincipalsInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<AddAaPrivateLinkPrincipalsInput>| 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 principal_type = input
.principal_type
.map(|pt| parse_principal_type(&pt))
.transpose()?;
let request = PrivateLinkAddPrincipalRequest {
principal: input.principal,
principal_type,
alias: input.alias,
};
let handler = PrivateLinkHandler::new(client);
let result = handler
.add_principals_active_active(input.subscription_id, input.region_id, &request)
.await
.tool_context("Failed to add AA PrivateLink principals")?;
CallToolResult::from_serialize(&result)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct RemoveAaPrivateLinkPrincipalsInput {
pub subscription_id: i32,
pub region_id: i32,
pub principal: String,
#[serde(default)]
pub principal_type: Option<String>,
#[serde(default)]
pub alias: Option<String>,
#[serde(default)]
pub profile: Option<String>,
}
pub fn remove_aa_private_link_principals(state: Arc<AppState>) -> Tool {
ToolBuilder::new("remove_aa_private_link_principals")
.description(
"Remove AWS principals from an Active-Active PrivateLink access list. \
Requires write permission.",
)
.non_destructive()
.extractor_handler_typed::<_, _, _, RemoveAaPrivateLinkPrincipalsInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<RemoveAaPrivateLinkPrincipalsInput>| 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 principal_type = input
.principal_type
.map(|pt| parse_principal_type(&pt))
.transpose()?;
let request =
redis_cloud::connectivity::private_link::PrivateLinkRemovePrincipalRequest {
principal: input.principal,
principal_type,
alias: input.alias,
};
let handler = PrivateLinkHandler::new(client);
let result = handler
.remove_principals_active_active(
input.subscription_id,
input.region_id,
&request,
)
.await
.tool_context("Failed to remove AA PrivateLink principals")?;
CallToolResult::from_serialize(&result)
},
)
.build()
}
#[derive(Debug, Deserialize, JsonSchema)]
pub struct GetAaPrivateLinkEndpointScriptInput {
pub subscription_id: i32,
pub region_id: i32,
#[serde(default)]
pub profile: Option<String>,
}
pub fn get_aa_private_link_endpoint_script(state: Arc<AppState>) -> Tool {
ToolBuilder::new("get_aa_private_link_endpoint_script")
.description(
"Get the endpoint creation script for an Active-Active AWS PrivateLink configuration.",
)
.read_only_safe()
.extractor_handler_typed::<_, _, _, GetAaPrivateLinkEndpointScriptInput>(
state,
|State(state): State<Arc<AppState>>,
Json(input): Json<GetAaPrivateLinkEndpointScriptInput>| 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 = PrivateLinkHandler::new(client);
let result = handler
.get_endpoint_script_active_active(input.subscription_id, input.region_id)
.await
.tool_context("Failed to get AA PrivateLink endpoint script")?;
CallToolResult::from_serialize(&result)
},
)
.build()
}
pub fn router(state: Arc<AppState>) -> McpRouter {
McpRouter::new()
.tool(get_vpc_peering(state.clone()))
.tool(create_vpc_peering(state.clone()))
.tool(update_vpc_peering(state.clone()))
.tool(delete_vpc_peering(state.clone()))
.tool(get_aa_vpc_peering(state.clone()))
.tool(create_aa_vpc_peering(state.clone()))
.tool(update_aa_vpc_peering(state.clone()))
.tool(delete_aa_vpc_peering(state.clone()))
.tool(get_tgw_attachments(state.clone()))
.tool(get_tgw_invitations(state.clone()))
.tool(accept_tgw_invitation(state.clone()))
.tool(reject_tgw_invitation(state.clone()))
.tool(create_tgw_attachment(state.clone()))
.tool(update_tgw_attachment_cidrs(state.clone()))
.tool(delete_tgw_attachment(state.clone()))
.tool(get_aa_tgw_attachments(state.clone()))
.tool(get_aa_tgw_invitations(state.clone()))
.tool(accept_aa_tgw_invitation(state.clone()))
.tool(reject_aa_tgw_invitation(state.clone()))
.tool(create_aa_tgw_attachment(state.clone()))
.tool(update_aa_tgw_attachment_cidrs(state.clone()))
.tool(delete_aa_tgw_attachment(state.clone()))
.tool(get_psc_service(state.clone()))
.tool(create_psc_service(state.clone()))
.tool(delete_psc_service(state.clone()))
.tool(get_psc_endpoints(state.clone()))
.tool(create_psc_endpoint(state.clone()))
.tool(update_psc_endpoint(state.clone()))
.tool(delete_psc_endpoint(state.clone()))
.tool(get_psc_creation_script(state.clone()))
.tool(get_psc_deletion_script(state.clone()))
.tool(get_aa_psc_service(state.clone()))
.tool(create_aa_psc_service(state.clone()))
.tool(delete_aa_psc_service(state.clone()))
.tool(get_aa_psc_endpoints(state.clone()))
.tool(create_aa_psc_endpoint(state.clone()))
.tool(update_aa_psc_endpoint(state.clone()))
.tool(delete_aa_psc_endpoint(state.clone()))
.tool(get_aa_psc_creation_script(state.clone()))
.tool(get_aa_psc_deletion_script(state.clone()))
.tool(get_private_link(state.clone()))
.tool(create_private_link(state.clone()))
.tool(delete_private_link(state.clone()))
.tool(add_private_link_principals(state.clone()))
.tool(remove_private_link_principals(state.clone()))
.tool(get_private_link_endpoint_script(state.clone()))
.tool(get_aa_private_link(state.clone()))
.tool(create_aa_private_link(state.clone()))
.tool(add_aa_private_link_principals(state.clone()))
.tool(remove_aa_private_link_principals(state.clone()))
.tool(get_aa_private_link_endpoint_script(state.clone()))
}