slave 0.13.1

CLI for slave, a Laravel-inspired Rust framework for MCP-first apps: scaffold an MCP backend with auth, roles, teams and SurrealDB migrations, add server-rendered web apps, and route them by domain or path
//! 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(),
        }))
    }
}