hanzo-api 1.1.11

Http Api for Hanzo AI platform
use async_channel::Sender;
use serde::{Deserialize, Serialize};
use hanzo_messages::schemas::mcp_server::{MCPServer, MCPServerEnv, MCPServerType};
use utoipa::{OpenApi, ToSchema};
use warp::Filter;

use crate::{node_api_router::APIError, node_commands::NodeCommand};

use super::api_v2_router::with_sender;

#[derive(Deserialize, Serialize, ToSchema, Debug)]
pub struct AddMCPServerRequest {
    pub name: String,
    pub r#type: MCPServerType,
    pub url: Option<String>,
    pub command: Option<String>,
    pub env: Option<MCPServerEnv>,
    pub is_enabled: bool,
}

#[derive(Deserialize, ToSchema, Debug)]
pub struct UpdateMCPServerRequest {
    pub id: i64,
    pub name: Option<String>,
    pub r#type: MCPServerType,
    pub url: Option<String>,
    pub command: Option<String>,
    pub env: Option<MCPServerEnv>,
    pub is_enabled: Option<bool>,
}

#[derive(Deserialize, ToSchema, Debug)]
pub struct ImportMCPServerFromGitHubRequest {
    pub github_url: String,
}

#[derive(Deserialize, ToSchema, Debug)]
pub struct GetAllMCPServerToolsRequest {
    pub mcp_server_id: i64,
}

#[derive(Deserialize, ToSchema, Debug)]
pub struct DeleteMCPServerRequest {
    pub mcp_server_id: i64,
}

#[derive(Deserialize, Serialize, ToSchema, Debug)]
pub struct DeleteMCPServerResponse {
    pub tools_deleted: i64,
    pub deleted_mcp_server: MCPServer,
    pub message: Option<String>,
}

#[derive(Deserialize, ToSchema, Debug)]
pub struct SetEnableMCPServerRequest {
    pub mcp_server_id: i64,
    pub is_enabled: bool,
}

pub fn mcp_server_routes(
    node_commands_sender: Sender<NodeCommand>,
) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
    let list_mcp_servers_route = warp::path("mcp_servers")
        .and(warp::get())
        .and(with_sender(node_commands_sender.clone()))
        .and(warp::header::<String>("authorization"))
        .and_then(list_mcp_servers_handler);

    let add_mcp_server_route = warp::path("add_mcp_server")
        .and(warp::post())
        .and(with_sender(node_commands_sender.clone()))
        .and(warp::header::<String>("authorization"))
        .and(warp::body::json())
        .and_then(add_mcp_server_handler);

    let update_mcp_server_route = warp::path("update_mcp_server")
        .and(warp::post())
        .and(with_sender(node_commands_sender.clone()))
        .and(warp::header::<String>("authorization"))
        .and(warp::body::json())
        .and_then(update_mcp_server_handler);

    let import_mcp_server_from_github_url_route = warp::path("import_mcp_server_from_github_url")
        .and(warp::post())
        .and(with_sender(node_commands_sender.clone()))
        .and(warp::header::<String>("authorization"))
        .and(warp::body::json())
        .and_then(import_mcp_server_from_github_url_handler);

    let get_all_mcp_server_tools_route = warp::path("mcp_server_tools")
        .and(warp::get())
        .and(with_sender(node_commands_sender.clone()))
        .and(warp::header::<String>("authorization"))
        .and(warp::query::<GetAllMCPServerToolsRequest>())
        .and_then(get_all_mcp_server_tools_handler);

    let delete_mcp_server_route = warp::path("delete_mcp_server")
        .and(warp::post())
        .and(with_sender(node_commands_sender.clone()))
        .and(warp::header::<String>("authorization"))
        .and(warp::body::json())
        .and_then(delete_mcp_server_handler);

    let set_enable_mcp_server_route = warp::path("set_enable_mcp_server")
        .and(warp::post())
        .and(with_sender(node_commands_sender.clone()))
        .and(warp::header::<String>("authorization"))
        .and(warp::body::json())
        .and_then(set_enable_mcp_server_handler);

    list_mcp_servers_route
        .or(add_mcp_server_route)
        .or(get_all_mcp_server_tools_route)
        .or(delete_mcp_server_route)
        .or(import_mcp_server_from_github_url_route)
        .or(set_enable_mcp_server_route)
        .or(update_mcp_server_route)
}

#[utoipa::path(
    get,
    path = "/v2/list_mcp_servers",
    responses(
        (status = 200, description = "Successfully retrieved MCP servers", body = Vec<MCPServer>),
        (status = 500, description = "Internal server error", body = APIError)
    )
)]
pub async fn list_mcp_servers_handler(
    sender: Sender<NodeCommand>,
    bearer: String,
) -> Result<impl warp::Reply, warp::Rejection> {
    let bearer = bearer.strip_prefix("Bearer ").unwrap_or("").to_string();
    let (res_sender, res_receiver) = async_channel::bounded(1);
    sender
        .send(NodeCommand::V2ApiListMCPServers {
            bearer,
            res: res_sender,
        })
        .await
        .map_err(|_| warp::reject::reject())?;

    let result = res_receiver.recv().await.map_err(|_| warp::reject::reject())?;

    match result {
        Ok(response) => Ok(warp::reply::json(&response)),
        Err(error) => Err(warp::reject::custom(error)),
    }
}

#[utoipa::path(
    post,
    path = "/v2/add_mcp_server",
    request_body = AddMCPServerRequest,
    responses(
        (status = 200, description = "Successfully added MCP server", body = String),
        (status = 400, description = "Bad request", body = APIError),
        (status = 500, description = "Internal server error", body = APIError)
    )
)]
pub async fn add_mcp_server_handler(
    sender: Sender<NodeCommand>,
    authorization: String,
    payload: AddMCPServerRequest,
) -> Result<impl warp::Reply, warp::Rejection> {
    let bearer = authorization.strip_prefix("Bearer ").unwrap_or("").to_string();
    let (res_sender, res_receiver) = async_channel::bounded(1);
    sender
        .send(NodeCommand::V2ApiAddMCPServer {
            bearer,
            mcp_server: payload,
            res: res_sender,
        })
        .await
        .map_err(|_| warp::reject::reject())?;

    let result = res_receiver.recv().await.map_err(|_| warp::reject::reject())?;

    match result {
        Ok(response) => Ok(warp::reply::json(&response)),
        Err(error) => Err(warp::reject::custom(error)),
    }
}

#[utoipa::path(
    post,
    path = "/v2/delete_mcp_server",
    request_body = DeleteMCPServerRequest,
    responses(
        (status = 200, description = "Successfully deleted MCP server", body = DeleteMCPServerResponse),
        (status = 400, description = "Bad request", body = APIError),
        (status = 500, description = "Internal server error", body = APIError)
    )
)]
pub async fn delete_mcp_server_handler(
    sender: Sender<NodeCommand>,
    authorization: String,
    payload: DeleteMCPServerRequest,
) -> Result<impl warp::Reply, warp::Rejection> {
    let bearer = authorization.strip_prefix("Bearer ").unwrap_or("").to_string();
    let (res_sender, res_receiver) = async_channel::bounded(1);
    sender
        .send(NodeCommand::V2ApiDeleteMCPServer {
            bearer,
            mcp_server_id: payload.mcp_server_id,
            res: res_sender,
        })
        .await
        .map_err(|_| warp::reject::reject())?;

    let result = res_receiver.recv().await.map_err(|_| warp::reject::reject())?;

    match result {
        Ok(response) => Ok(warp::reply::json(&response)),
        Err(error) => Err(warp::reject::custom(error)),
    }
}

#[utoipa::path(
    get,
    path = "/v2/get_all_mcp_server_tools",
    responses(
        (status = 200, description = "Successfully retrieved MCP server tools", body = Vec<HanzoTool>),
        (status = 400, description = "Bad request", body = APIError),
        (status = 500, description = "Internal server error", body = APIError)
    )
)]
pub async fn get_all_mcp_server_tools_handler(
    sender: Sender<NodeCommand>,
    authorization: String,
    payload: GetAllMCPServerToolsRequest,
) -> Result<impl warp::Reply, warp::Rejection> {
    let bearer = authorization.strip_prefix("Bearer ").unwrap_or("").to_string();
    let (res_sender, res_receiver) = async_channel::bounded(1);
    sender
        .send(NodeCommand::V2ApiGetAllMCPServerTools {
            bearer,
            mcp_server_id: payload.mcp_server_id,
            res: res_sender,
        })
        .await
        .map_err(|_| warp::reject::reject())?;

    let result = res_receiver.recv().await.map_err(|_| warp::reject::reject())?;

    match result {
        Ok(response) => Ok(warp::reply::json(&response)),
        Err(error) => Err(warp::reject::custom(error)),
    }
}

#[utoipa::path(
    post,
    path = "/v2/import_mcp_server_from_github_url",
    request_body = ImportMCPServerFromGitHubRequest,
    responses(
        (status = 200, description = "Successfully imported MCP server", body = AddMCPServerRequest),
        (status = 400, description = "Bad request", body = APIError),
        (status = 500, description = "Internal server error", body = APIError)
    )
)]
pub async fn import_mcp_server_from_github_url_handler(
    sender: Sender<NodeCommand>,
    authorization: String,
    payload: ImportMCPServerFromGitHubRequest,
) -> Result<impl warp::Reply, warp::Rejection> {
    let bearer = authorization.strip_prefix("Bearer ").unwrap_or("").to_string();
    let (res_sender, res_receiver) = async_channel::bounded(1);
    sender
        .send(NodeCommand::V2ApiImportMCPServerFromGitHubURL {
            bearer,
            github_url: payload.github_url,
            res: res_sender,
        })
        .await
        .map_err(|_| warp::reject::reject())?;

    let result = res_receiver.recv().await.map_err(|_| warp::reject::reject())?;

    match result {
        Ok(response) => Ok(warp::reply::json(&response)),
        Err(error) => Err(warp::reject::custom(error)),
    }
}

#[utoipa::path(
    post,
    path = "/v2/set_enable_mcp_server",
    request_body = SetEnableMCPServerRequest,
    responses(
        (status = 200, description = "Successfully set enable MCP server", body = MCPServer),
        (status = 400, description = "Bad request", body = APIError),
        (status = 500, description = "Internal server error", body = APIError)
    )
)]
pub async fn set_enable_mcp_server_handler(
    sender: Sender<NodeCommand>,
    authorization: String,
    payload: SetEnableMCPServerRequest,
) -> Result<impl warp::Reply, warp::Rejection> {
    let bearer = authorization.strip_prefix("Bearer ").unwrap_or("").to_string();
    let (res_sender, res_receiver) = async_channel::bounded(1);
    sender
        .send(NodeCommand::V2ApiSetEnableMCPServer {
            bearer,
            mcp_server_id: payload.mcp_server_id,
            is_enabled: payload.is_enabled,
            res: res_sender,
        })
        .await
        .map_err(|_| warp::reject::reject())?;

    let result = res_receiver.recv().await.map_err(|_| warp::reject::reject())?;

    match result {
        Ok(response) => Ok(warp::reply::json(&response)),
        Err(error) => Err(warp::reject::custom(error)),
    }
}

#[utoipa::path(
    post,
    path = "/v2/update_mcp_server",
    request_body = UpdateMCPServerRequest,
    responses(
        (status = 200, description = "Successfully updated MCP server", body = MCPServer),
        (status = 400, description = "Bad request", body = APIError),
        (status = 500, description = "Internal server error", body = APIError)
    )
)]
pub async fn update_mcp_server_handler(
    sender: Sender<NodeCommand>,
    authorization: String,
    payload: UpdateMCPServerRequest,
) -> Result<impl warp::Reply, warp::Rejection> {
    let bearer = authorization.strip_prefix("Bearer ").unwrap_or("").to_string();
    let (res_sender, res_receiver) = async_channel::bounded(1);
    sender
        .send(NodeCommand::V2ApiUpdateMCPServer {
            bearer,
            mcp_server: payload,
            res: res_sender,
        })
        .await
        .map_err(|_| warp::reject::reject())?;

    let result = res_receiver.recv().await.map_err(|_| warp::reject::reject())?;

    match result {
        Ok(response) => Ok(warp::reply::json(&response)),
        Err(error) => Err(warp::reject::custom(error)),
    }
}

#[derive(OpenApi)]
#[openapi(
    paths(
        list_mcp_servers_handler,
        add_mcp_server_handler,
        get_all_mcp_server_tools_handler,
        import_mcp_server_from_github_url_handler,
        delete_mcp_server_handler,
        set_enable_mcp_server_handler,
        update_mcp_server_handler,
    ),
    components(
        schemas(AddMCPServerRequest, MCPServer, APIError)
    ),
    tags(
        (name = "mcp_servers", description = "MCP Server API endpoints")
    )
)]
pub struct MCPServerApiDoc;