//! MCP tools for apps and their owner teams. Business rules live in
//! `AppService`.
use axum::extract::FromRef;
use axum::http::request::Parts;
use rmcp::handler::server::router::tool::ToolRouter;
use rmcp::handler::server::tool::Extension;
use rmcp::handler::server::wrapper::{Json, Parameters};
use rmcp::{tool, tool_router};
use crate::base::error::AppResult;
use crate::base::mcp::McpServer;
use crate::modules::app::dto::app::{
AppAccessResponse, AppListResponse, AppRequest, AppResponse, AssignAppRequest,
};
use crate::modules::app::services::app_service::AppService;
pub fn tools() -> ToolRouter<McpServer> {
McpServer::app_tools()
}
#[tool_router(router = app_tools, vis = "pub(crate)")]
impl McpServer {
/// The apps owned by the caller's teams (every app for super admins).
#[tool(annotations(read_only_hint = true, open_world_hint = false))]
async fn app_list(
&self,
Extension(parts): Extension<Parts>,
) -> AppResult<Json<AppListResponse>> {
let user = self.authenticate(&parts).await?;
let apps = AppService::from_ref(&self.state).list(&user).await?;
Ok(Json(AppListResponse {
apps: apps.into_iter().map(AppResponse::from).collect(),
}))
}
/// The caller's access to an app: their role and modules in the team that
/// owns it. Apps call this to decide what a signed-in user may do.
#[tool(annotations(read_only_hint = true, open_world_hint = false))]
async fn app_access(
&self,
Extension(parts): Extension<Parts>,
Parameters(input): Parameters<AppRequest>,
) -> AppResult<Json<AppAccessResponse>> {
let user = self.authenticate(&parts).await?;
let access = AppService::from_ref(&self.state)
.access(&user, input.app.trim())
.await?;
Ok(Json(access.into()))
}
/// Give an app to a team, or take it back (super admin only).
#[tool(annotations(idempotent_hint = true, open_world_hint = false))]
async fn app_assign(
&self,
Extension(parts): Extension<Parts>,
Parameters(input): Parameters<AssignAppRequest>,
) -> AppResult<Json<AppListResponse>> {
let user = self.authenticate(&parts).await?;
let service = AppService::from_ref(&self.state);
service
.assign(&user, input.app.trim(), input.team_id)
.await?;
let apps = service.list(&user).await?;
Ok(Json(AppListResponse {
apps: apps.into_iter().map(AppResponse::from).collect(),
}))
}
}