@@ -730,11 +730,30 @@ impl rmcp::handler::server::ServerHandler for EngraphServer {
730730 }
731731}
732732
733+ // ---------------------------------------------------------------------------
734+ // HTTP server options (populated by CLI flags in Task 7)
735+ // ---------------------------------------------------------------------------
736+
737+ pub struct HttpServeOpts {
738+ pub port : u16 ,
739+ pub host : String ,
740+ pub no_auth : bool ,
741+ }
742+
733743// ---------------------------------------------------------------------------
734744// Entry point
735745// ---------------------------------------------------------------------------
736746
737- pub async fn run_serve ( data_dir : & Path ) -> Result < ( ) > {
747+ pub async fn run_serve ( data_dir : & Path , http_opts : Option < HttpServeOpts > ) -> Result < ( ) > {
748+ if let Some ( ref opts) = http_opts {
749+ if opts. no_auth && opts. host != "127.0.0.1" {
750+ anyhow:: bail!(
751+ "--no-auth cannot be used with --host {} (only 127.0.0.1 is allowed)" ,
752+ opts. host
753+ ) ;
754+ }
755+ }
756+
738757 let db_path = data_dir. join ( "engraph.db" ) ;
739758 let models_dir = data_dir. join ( "models" ) ;
740759
@@ -800,6 +819,15 @@ pub async fn run_serve(data_dir: &Path) -> Result<()> {
800819 let profile_arc = Arc :: new ( profile) ;
801820 let recent_writes: RecentWrites = Arc :: new ( Mutex :: new ( HashMap :: new ( ) ) ) ;
802821
822+ // Clone Arcs for HTTP server before MCP consumes them
823+ let http_store = store_arc. clone ( ) ;
824+ let http_embedder = embedder_arc. clone ( ) ;
825+ let http_vault_path = vault_path_arc. clone ( ) ;
826+ let http_profile = profile_arc. clone ( ) ;
827+ let http_orchestrator = orchestrator. as_ref ( ) . map ( Arc :: clone) ;
828+ let http_reranker = reranker. as_ref ( ) . map ( Arc :: clone) ;
829+ let http_recent_writes = recent_writes. clone ( ) ;
830+
803831 // Start file watcher for real-time index updates
804832 let mut exclude = config. exclude . clone ( ) ;
805833 if let Some ( ref prof) = * profile_arc
@@ -831,12 +859,45 @@ pub async fn run_serve(data_dir: &Path) -> Result<()> {
831859 recent_writes,
832860 } ;
833861
862+ // Cancellation token for coordinated shutdown of HTTP + MCP
863+ let cancel_token = tokio_util:: sync:: CancellationToken :: new ( ) ;
864+
865+ // Spawn HTTP server as a background task (before MCP blocks on stdio)
866+ if let Some ( ref opts) = http_opts {
867+ let config = Config :: load ( ) ?;
868+ let api_state = crate :: http:: ApiState {
869+ store : http_store,
870+ embedder : http_embedder,
871+ vault_path : http_vault_path,
872+ profile : http_profile,
873+ orchestrator : http_orchestrator,
874+ reranker : http_reranker,
875+ http_config : Arc :: new ( config. http . clone ( ) ) ,
876+ no_auth : opts. no_auth ,
877+ recent_writes : http_recent_writes,
878+ rate_limiter : Arc :: new ( crate :: http:: RateLimiter :: new ( config. http . rate_limit ) ) ,
879+ } ;
880+ let router = crate :: http:: build_router ( api_state) ;
881+ let addr = format ! ( "{}:{}" , opts. host, opts. port) ;
882+ let listener = tokio:: net:: TcpListener :: bind ( & addr) . await ?;
883+ let cancel = cancel_token. clone ( ) ;
884+ eprintln ! ( "HTTP server listening on http://{}" , addr) ;
885+ tokio:: spawn ( async move {
886+ axum:: serve ( listener, router)
887+ . with_graceful_shutdown ( cancel. cancelled_owned ( ) )
888+ . await
889+ . ok ( ) ;
890+ } ) ;
891+ }
892+
834893 eprintln ! ( "engraph MCP server starting..." ) ;
835894
836895 let transport = rmcp:: transport:: io:: stdio ( ) ;
837896 let server_handle = server. serve ( transport) . await ?;
838897 server_handle. waiting ( ) . await ?;
839898
899+ cancel_token. cancel ( ) ; // triggers HTTP graceful shutdown
900+
840901 // Shut down watcher cleanly after MCP transport exits
841902 let _ = watcher_shutdown. send ( ( ) ) ;
842903 if let Err ( e) = watcher_handle. join ( ) {
0 commit comments