use std::net::SocketAddr;
use std::sync::Arc;
use mnemo_mcp::server::MnemoServer;
use rmcp::transport::streamable_http_server::{
StreamableHttpService, session::local::LocalSessionManager,
};
pub const MCP_PATH: &str = "/mcp";
pub async fn serve(
server: MnemoServer,
port: u16,
has_capability_issuer: bool,
) -> Result<(), Box<dyn std::error::Error>> {
if !has_capability_issuer {
return Err(
"--http-port requires --capability-key (or MNEMO_CAPABILITY_KEY).\n\
\n\
Without a key the server cannot verify per-request capabilities, so every \
caller on this port would resolve to the boot --agent-id: a multi-caller \
transport with a single-caller identity model. Refusing to start rather \
than serve that."
.into(),
);
}
let session_manager = Arc::new(LocalSessionManager::default());
let mut config = rmcp::transport::streamable_http_server::StreamableHttpServerConfig::default();
config.json_response = true;
let service = StreamableHttpService::new(move || Ok(server.clone()), session_manager, config);
let app = axum::Router::new().nest_service(MCP_PATH, service);
let addr = SocketAddr::from(([127, 0, 0, 1], port));
let listener =
tokio::net::TcpListener::bind(addr)
.await
.map_err(|e| -> Box<dyn std::error::Error> {
format!("binding MCP HTTP transport on {addr}: {e}").into()
})?;
tracing::info!("Starting Mnemo MCP server on http://{addr}{MCP_PATH} (Streamable HTTP)");
tracing::info!(
"Per-request identity is ON: callers authenticate with \
`Authorization: Bearer <base64url capability>`"
);
axum::serve(listener, app)
.await
.map_err(|e| -> Box<dyn std::error::Error> {
format!("MCP HTTP transport terminated: {e}").into()
})?;
Ok(())
}