package app import ( "context" "errors" "log/slog" "net/http" "time" "gitea.thetempleofdoom.com/drjones/mcp-gateway-nexus/internal/config" "gitea.thetempleofdoom.com/drjones/mcp-gateway-nexus/internal/gateway" ) // Serve builds the router from config, connects upstreams, and runs the HTTP // gateway until ctx is cancelled, then shuts down gracefully. func Serve(ctx context.Context, cfg *config.Config, log *slog.Logger) error { r, err := BuildRouter(cfg, log) if err != nil { return err } defer r.Close() log.Info("connecting upstreams", "count", len(cfg.Upstreams)) connectCtx, cancel := context.WithTimeout(ctx, 30*time.Second) r.ConnectAll(connectCtx) cancel() for _, s := range r.Statuses() { if s.Ready { log.Info("upstream ready", "namespace", s.Namespace, "server", s.Server) } else { log.Warn("upstream unavailable", "namespace", s.Namespace, "error", s.Error) } } gw := gateway.New(gateway.Options{ Name: cfg.Gateway.Name, Version: cfg.Gateway.Version, MCPPath: cfg.Gateway.MCPPath, Router: r, Logger: log, }) srv := &http.Server{ Addr: cfg.Server.Address, Handler: gw.Handler(), ReadHeaderTimeout: 10 * time.Second, } errCh := make(chan error, 1) go func() { log.Info("gateway listening", "address", cfg.Server.Address, "mcp_endpoint", cfg.Gateway.MCPPath, "tools", len(r.ListTools())) if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { errCh <- err } }() select { case <-ctx.Done(): log.Info("shutting down") shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() return srv.Shutdown(shutdownCtx) case err := <-errCh: return err } }