macro_rules! database_tool {
(@guard no_guard $state:ident) => {};
(@guard write_guard $state:ident) => {
if !$state.is_write_allowed() {
return Err(tower_mcp::Error::tool(
"Write operations not allowed in read-only mode",
));
}
};
(@guard destructive_guard $state:ident) => {
if !$state.is_destructive_allowed() {
return Err(tower_mcp::Error::tool(
"Destructive operations require policy tier 'full'",
));
}
};
(@impl $safety_method:ident, $guard:ident, $fn_name:ident, $tool_name:literal, $description:expr,
{ $($(#[$field_meta:meta])* pub $field_name:ident : $field_type:ty),* $(,)? }
=> |$conn:ident, $input:ident| $body:block
) => {
pastey::paste! {
#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
pub struct [<$fn_name:camel Input>] {
#[serde(default)]
pub url: Option<String>,
#[serde(default)]
pub profile: Option<String>,
$(
$(#[$field_meta])*
pub $field_name: $field_type,
)*
}
pub fn $fn_name(state: std::sync::Arc<crate::state::AppState>) -> tower_mcp::Tool {
tower_mcp::ToolBuilder::new($tool_name)
.description($description)
.$safety_method()
.extractor_handler(
state,
|tower_mcp::extract::State(state): tower_mcp::extract::State<std::sync::Arc<crate::state::AppState>>,
tower_mcp::extract::Json(mut $input): tower_mcp::extract::Json<[<$fn_name:camel Input>]>| async move {
database_tool!(@guard $guard state);
#[allow(unused_mut)]
let mut $conn = super::get_connection(
$input.url.take(), $input.profile.as_deref(), &state
).await?;
#[allow(unused_variables)]
let state = &state;
$body
},
)
.build()
}
}
};
(read_only, $($rest:tt)*) => {
database_tool!(@impl read_only_safe, no_guard, $($rest)*);
};
(write, $($rest:tt)*) => {
database_tool!(@impl non_destructive, write_guard, $($rest)*);
};
(destructive, $($rest:tt)*) => {
database_tool!(@impl destructive, destructive_guard, $($rest)*);
};
}
pub(crate) use database_tool;
#[allow(unused_macros)]
macro_rules! cloud_tool {
(@guard no_guard $state:ident) => {};
(@guard write_guard $state:ident) => {
if !$state.is_write_allowed() {
return Err(tower_mcp::Error::tool(
"Write operations not allowed in read-only mode",
));
}
};
(@guard destructive_guard $state:ident) => {
if !$state.is_destructive_allowed() {
return Err(tower_mcp::Error::tool(
"Destructive operations require policy tier 'full'",
));
}
};
(@impl $safety_method:ident, $guard:ident, $fn_name:ident, $tool_name:literal, $description:expr,
{ $($(#[$field_meta:meta])* pub $field_name:ident : $field_type:ty),* $(,)? }
=> |$client:ident, $input:ident| $body:block
) => {
pastey::paste! {
#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
pub struct [<$fn_name:camel Input>] {
#[serde(default)]
pub profile: Option<String>,
$(
$(#[$field_meta])*
pub $field_name: $field_type,
)*
}
pub fn $fn_name(state: std::sync::Arc<crate::state::AppState>) -> tower_mcp::Tool {
tower_mcp::ToolBuilder::new($tool_name)
.description($description)
.$safety_method()
.extractor_handler(
state,
|tower_mcp::extract::State(state): tower_mcp::extract::State<std::sync::Arc<crate::state::AppState>>,
tower_mcp::extract::Json($input): tower_mcp::extract::Json<[<$fn_name:camel Input>]>| async move {
cloud_tool!(@guard $guard state);
let $client = state
.cloud_client_for_profile($input.profile.as_deref())
.await
.map_err(|e| crate::tools::credential_error("cloud", e))?;
#[allow(unused_variables)]
let state = &state;
$body
},
)
.build()
}
}
};
(read_only, $($rest:tt)*) => {
cloud_tool!(@impl read_only_safe, no_guard, $($rest)*);
};
(write, $($rest:tt)*) => {
cloud_tool!(@impl non_destructive, write_guard, $($rest)*);
};
(destructive, $($rest:tt)*) => {
cloud_tool!(@impl destructive, destructive_guard, $($rest)*);
};
}
#[allow(unused_imports)]
pub(crate) use cloud_tool;
#[allow(unused_macros)]
macro_rules! enterprise_tool {
(@guard no_guard $state:ident) => {};
(@guard write_guard $state:ident) => {
if !$state.is_write_allowed() {
return Err(tower_mcp::Error::tool(
"Write operations not allowed in read-only mode",
));
}
};
(@guard destructive_guard $state:ident) => {
if !$state.is_destructive_allowed() {
return Err(tower_mcp::Error::tool(
"Destructive operations require policy tier 'full'",
));
}
};
(@impl $safety_method:ident, $guard:ident, $fn_name:ident, $tool_name:literal, $description:expr,
{ $($(#[$field_meta:meta])* pub $field_name:ident : $field_type:ty),* $(,)? }
=> |$client:ident, $input:ident| $body:block
) => {
pastey::paste! {
#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
pub struct [<$fn_name:camel Input>] {
#[serde(default)]
pub profile: Option<String>,
$(
$(#[$field_meta])*
pub $field_name: $field_type,
)*
}
pub fn $fn_name(state: std::sync::Arc<crate::state::AppState>) -> tower_mcp::Tool {
tower_mcp::ToolBuilder::new($tool_name)
.description($description)
.$safety_method()
.extractor_handler(
state,
|tower_mcp::extract::State(state): tower_mcp::extract::State<std::sync::Arc<crate::state::AppState>>,
tower_mcp::extract::Json($input): tower_mcp::extract::Json<[<$fn_name:camel Input>]>| async move {
enterprise_tool!(@guard $guard state);
let $client = state
.enterprise_client_for_profile($input.profile.as_deref())
.await
.map_err(|e| crate::tools::credential_error("enterprise", e))?;
#[allow(unused_variables)]
let state = &state;
$body
},
)
.build()
}
}
};
(read_only, $($rest:tt)*) => {
enterprise_tool!(@impl read_only_safe, no_guard, $($rest)*);
};
(write, $($rest:tt)*) => {
enterprise_tool!(@impl non_destructive, write_guard, $($rest)*);
};
(destructive, $($rest:tt)*) => {
enterprise_tool!(@impl destructive, destructive_guard, $($rest)*);
};
}
#[allow(unused_imports)]
pub(crate) use enterprise_tool;
macro_rules! mcp_module {
{ $( $fn_name:ident => $tool_name:literal ),* $(,)? } => {
pub(super) const TOOL_NAMES: &[&str] = &[$($tool_name),*];
pub fn router(state: std::sync::Arc<crate::state::AppState>) -> tower_mcp::McpRouter {
tower_mcp::McpRouter::new()
$(
.tool($fn_name(state.clone()))
)*
}
};
}
pub(crate) use mcp_module;