slave 0.4.0

Laravel-inspired CLI for Rust MCP servers: scaffold apps whose business operations are MCP tools over Streamable HTTP, with built-in auth, users, roles and Turso migrations
//! The MCP server: the application's only business interface.
//!
//! Every business operation is an MCP tool, served over Streamable HTTP at
//! `/mcp`. Domain modules contribute tools from their `tools.rs`; they are
//! combined once at startup by `crate::modules::tools()`.
//!
//! The endpoint runs statelessly (no MCP sessions) and answers plain
//! request/response calls with `application/json`, so any instance can serve
//! any request.

use std::sync::Arc;

use axum::http::request::Parts;
use rmcp::handler::server::router::tool::ToolRouter;
use rmcp::model::{Implementation, ServerCapabilities, ServerConfig};
use rmcp::transport::streamable_http_server::session::never::NeverSessionManager;
use rmcp::transport::streamable_http_server::{StreamableHttpServerConfig, StreamableHttpService};
use rmcp::{ServerHandler, tool_handler};

use super::auth::AuthUser;
use super::error::AppResult;
use super::state::AppState;
use crate::modules;

/// Path the MCP endpoint is mounted at.
pub const PATH: &str = "/mcp";

/// Handles MCP requests. A cheap clone is made for every request.
#[derive(Clone)]
pub struct McpServer {
    pub state: AppState,
    tools: Arc<ToolRouter<McpServer>>,
}

impl McpServer {
    /// Resolves the caller from the request's `Authorization: Bearer` header.
    pub async fn authenticate(&self, parts: &Parts) -> AppResult<AuthUser> {
        AuthUser::authenticate(&self.state, &parts.headers).await
    }

    /// Like [`Self::authenticate`], but also requires the `admin` role.
    pub async fn authenticate_admin(&self, parts: &Parts) -> AppResult<AuthUser> {
        let user = self.authenticate(parts).await?;
        user.require_admin()?;
        Ok(user)
    }
}

#[tool_handler(router = self.tools)]
impl ServerHandler for McpServer {
    fn get_info(&self) -> ServerConfig {
        ServerConfig::new(ServerCapabilities::builder().enable_tools().build())
            .with_server_info(Implementation::new(
                self.state.config.name.clone(),
                env!("CARGO_PKG_VERSION"),
            ))
            .with_instructions(
                "Call auth_register or auth_login to obtain a bearer token, then send it \
                 as `Authorization: Bearer <token>` on every request. The first account \
                 registered becomes admin.",
            )
    }
}

/// The Streamable HTTP service to mount at [`PATH`].
pub fn service(state: AppState) -> StreamableHttpService<McpServer, NeverSessionManager> {
    let config = StreamableHttpServerConfig::default()
        .with_legacy_session_mode(false)
        .with_json_response(true)
        .with_sse_keep_alive(None)
        .with_allowed_hosts(state.config.mcp_allowed_hosts.clone());
    let server = McpServer {
        state,
        tools: Arc::new(modules::tools()),
    };
    StreamableHttpService::new(move || Ok(server.clone()), Default::default(), config)
}