AkurAI Build
Menu

Public repository / 38337fdd6069cbcb0ce0327fb09aa0b474a34e76

AkurAI-Notes / crates/notes-mcp/src/server.rs

Latest change 38337fdd6069cbcb0ce0327fb09aa0b474a34e76 - fix(mcp): embed notes off-request via background queue by Ólafur Búi Ólafsson

use crate::tools::ToolRegistry;
use akurai_http::{Method, Request, Response};
use akurai_storage::BTree;
use std::sync::{Arc, Mutex};

pub struct McpConfig {
    pub host: String,
    pub port: u16,
    pub db: String,
    pub token: String,
    pub embed_url: Option<String>,
    pub embed_model: Option<String>,
    pub embed_api_key: Option<String>,
    pub owner_sub: String,
}

pub struct McpServer {
    config: McpConfig,
    tools: Arc<ToolRegistry>,
}

impl McpServer {
    pub fn new(config: McpConfig) -> Result<Self, String> {
        let btree =
            BTree::open(&config.db).map_err(|e| format!("failed to open MCP database: {e}"))?;
        Ok(Self::with_db(config, Arc::new(Mutex::new(btree))))
    }

    pub fn with_db(config: McpConfig, db: Arc<Mutex<BTree>>) -> Self {
        let tools = ToolRegistry::new_background(
            db,
            config.embed_url.clone(),
            config.embed_model.clone(),
            config.embed_api_key.clone(),
            config.owner_sub.clone(),
        );
        Self {
            config,
            tools: Arc::new(tools),
        }
    }

    pub fn handle_request(&self, body: &str) -> String {
        self.tools.handle_request(body)
    }

    pub fn handle_http(&self, req: &Request) -> Response {
        handle_http(&self.config.token, &self.tools, req)
    }

    pub fn serve(&self) -> Result<(), String> {
        let addr = format!("{}:{}", self.config.host, self.config.port);
        println!("akurai-notes MCP server listening on http://{addr}/mcp");

        let token = self.config.token.clone();
        let tools = Arc::clone(&self.tools);
        let handler = move |req: &Request| -> Response { handle_http(&token, &tools, req) };

        let server = akurai_http::Server::bind(&addr)
            .map_err(|e| format!("failed to bind MCP server: {e}"))?;
        server
            .run(handler)
            .map_err(|e| format!("server error: {e}"))
    }
}

fn handle_http(token: &str, tools: &ToolRegistry, req: &Request) -> Response {
    if req.path != "/mcp" {
        return Response::new(404).with_text("Not found");
    }
    if req.method != Method::Post {
        return Response::new(405).with_text("Method not allowed");
    }
    if !token.is_empty() {
        let authorized = req
            .header("Authorization")
            .and_then(|s| s.strip_prefix("Bearer "))
            .map(|t| t.trim() == token)
            .unwrap_or(false);
        if !authorized {
            return Response::new(401).with_text("unauthorized");
        }
    }

    let result = tools.handle_request(req.body_str().as_ref());
    if result.is_empty() {
        return Response::new(202).with_header("Access-Control-Allow-Origin", "*");
    }

    Response::ok()
        .with_header("Access-Control-Allow-Origin", "*")
        .with_header("Content-Type", "application/json")
        .with_body("application/json", result.into_bytes())
}