use serde_json::Value;
use tower_mcp::{CallToolResult, ResultExt};
use crate::tools::macros::{enterprise_tool, mcp_module};
mcp_module! {
list_services => "list_enterprise_services",
get_service => "get_enterprise_service",
get_service_status => "get_enterprise_service_status",
update_service => "update_enterprise_service",
start_service => "start_enterprise_service",
stop_service => "stop_enterprise_service",
restart_service => "restart_enterprise_service",
}
fn lookup_service(services: &Value, service_id: &str) -> Result<Value, tower_mcp::Error> {
let map = services.as_object().ok_or_else(|| {
tower_mcp::Error::tool("Unexpected response format from /v1/local/services".to_string())
})?;
map.get(service_id).cloned().ok_or_else(|| {
let available: Vec<&str> = map.keys().map(String::as_str).collect();
tower_mcp::Error::tool(format!(
"Service '{}' not found. Available services: {}",
service_id,
available.join(", ")
))
})
}
enterprise_tool!(read_only, list_services, "list_enterprise_services",
"List all cluster services.",
{} => |client, _input| {
let services: Value = client
.get("/v1/local/services")
.await
.tool_context("Failed to list services")?;
CallToolResult::from_serialize(&services)
}
);
enterprise_tool!(read_only, get_service, "get_enterprise_service",
"Get service details by ID.",
{
pub service_id: String,
} => |client, input| {
let services: Value = client
.get("/v1/local/services")
.await
.tool_context("Failed to get service")?;
let service = lookup_service(&services, &input.service_id)?;
CallToolResult::from_serialize(&service)
}
);
enterprise_tool!(read_only, get_service_status, "get_enterprise_service_status",
"Get service status including per-node status.",
{
pub service_id: String,
} => |client, input| {
let services: Value = client
.get("/v1/local/services")
.await
.tool_context("Failed to get service status")?;
let service = lookup_service(&services, &input.service_id)?;
CallToolResult::from_serialize(&service)
}
);
enterprise_tool!(write, update_service, "update_enterprise_service",
"Update a service's configuration. Pass fields as JSON.",
{
pub service_id: String,
pub config: Value,
} => |client, input| {
let result: Value = client
.put_raw(&format!("/v1/services/{}", input.service_id), input.config)
.await
.tool_context("Failed to update service")?;
CallToolResult::from_serialize(&result)
}
);
enterprise_tool!(write, start_service, "start_enterprise_service",
"Start a stopped service.",
{
pub service_id: String,
} => |client, input| {
let result: Value = client
.post_raw(
&format!("/v1/services/{}/start", input.service_id),
serde_json::json!({}),
)
.await
.tool_context("Failed to start service")?;
CallToolResult::from_serialize(&result)
}
);
enterprise_tool!(write, stop_service, "stop_enterprise_service",
"Stop a running service.",
{
pub service_id: String,
} => |client, input| {
let result: Value = client
.post_raw(
&format!("/v1/services/{}/stop", input.service_id),
serde_json::json!({}),
)
.await
.tool_context("Failed to stop service")?;
CallToolResult::from_serialize(&result)
}
);
enterprise_tool!(write, restart_service, "restart_enterprise_service",
"Restart a service (stop then start).",
{
pub service_id: String,
} => |client, input| {
let result: Value = client
.post_raw(
&format!("/v1/services/{}/restart", input.service_id),
serde_json::json!({}),
)
.await
.tool_context("Failed to restart service")?;
CallToolResult::from_serialize(&result)
}
);