Menu
Public repository / 58558732228cea9b646049555021e899ee300f35
AkurAI-PassVault / crates/passvault-mcp/src/server.rs
Latest change 2ec23d99ad43671c32257c5f6f9e58d12ef8d7bf - feat: add standardized /api/health endpoint to MCP server by Claude Code
use std::sync::{Arc, Mutex};
use akurai_http::{Response, Server};
use akurai_storage::BTree;
use passvault_core::crypto::Crypto;
use crate::tools::ToolRegistry;
pub struct McpConfig {
pub host: String,
pub port: u16,
pub db_path: String,
pub token: String,
pub encryption_key: String,
}
pub struct McpServer {
config: McpConfig,
}
impl McpServer {
pub fn new(config: McpConfig) -> Self {
McpServer { config }
}
pub fn serve(&self) -> Result<(), String> {
let db = BTree::open(&self.config.db_path)
.map_err(|e| format!("failed to open db: {e}"))?;
let db = Arc::new(Mutex::new(db));
let crypto = Crypto::from_hex_key(&self.config.encryption_key)
.map_err(|e| format!("crypto init: {e}"))?;
let crypto = Arc::new(crypto);
let token = self.config.token.clone();
let registry = Arc::new(ToolRegistry::new(db, crypto));
let handler = move |req: &akurai_http::Request| -> Response {
if req.path == "/api/health" && req.method == akurai_http::Method::Get {
let body = format!(r#"{{"app":"akurai-passvault","version":"{}","status":"ok"}}"#, env!("CARGO_PKG_VERSION"));
return Response::new(200)
.with_header("Content-Type", "application/json")
.with_body("application/json", body.into_bytes());
}
if req.path != "/mcp" || req.method != akurai_http::Method::Post {
return Response::new(404).with_text("not found");
}
let auth = req.header("authorization").unwrap_or("");
let expected = format!("Bearer {token}");
if auth != expected {
return Response::new(401).with_text("unauthorized");
}
let body = req.body_str();
let result = registry.handle_request(&body);
// Empty result = JSON-RPC notification: acknowledge with 202 + empty body
// (MCP Streamable-HTTP spec). Strict clients (codex/rmcp) break if a
// notification receives a JSON-RPC response body.
if result.is_empty() {
return Response::new(202);
}
Response::new(200)
.with_header("Content-Type", "application/json")
.with_body("application/json", result.into_bytes())
};
let addr = format!("{}:{}", self.config.host, self.config.port);
Server::bind(&addr)
.map_err(|e| format!("bind: {e}"))?
.run(handler)
.map_err(|e| format!("server: {e}"))
}
}