pub mod context_pack;
use anyhow::{Context, Result};
use tokio_util::sync::CancellationToken;
use tracing::info;
use crate::LoctreeServer;
pub async fn serve_http(bind: &str) -> Result<()> {
use rmcp::transport::streamable_http_server::{
StreamableHttpServerConfig, StreamableHttpService, session::local::LocalSessionManager,
};
let cancel = CancellationToken::new();
let config = StreamableHttpServerConfig::default().with_cancellation_token(cancel.clone());
let service: StreamableHttpService<LoctreeServer, LocalSessionManager> =
StreamableHttpService::new(
|| Ok(LoctreeServer::new()),
std::sync::Arc::new(LocalSessionManager::default()),
config,
);
let router = axum::Router::new()
.route(
"/context_pack",
axum::routing::get(context_pack::context_pack_handler),
)
.nest_service("/mcp", service);
let listener = tokio::net::TcpListener::bind(bind)
.await
.with_context(|| format!("could not bind streamable-http server on {bind}"))?;
let local = listener.local_addr().ok();
if let Some(addr) = local {
use std::io::Write as _;
let mut stdout = std::io::stdout();
let _ = writeln!(stdout, "loctree-mcp http listening on {addr}");
let _ = stdout.flush();
}
info!(
"Server ready. Streamable-http MCP at http://{}/mcp",
local
.map(|a| a.to_string())
.unwrap_or_else(|| bind.to_string())
);
let shutdown = {
let cancel = cancel.clone();
async move {
let _ = tokio::signal::ctrl_c().await;
cancel.cancel();
}
};
axum::serve(listener, router)
.with_graceful_shutdown(shutdown)
.await
.with_context(|| "axum::serve exited with error")?;
Ok(())
}