// Command fjmcp-broker is an OAuth 2.1 authorization server and MCP session // broker that fronts forgejo-mcp. See ../../README.md and ../../docs/ for // the design. package main import ( "context" "errors" "flag" "fmt" "io" "os" "os/signal" "syscall" "kode.naiv.no/olemd/forgejo-mcp-broker/internal/buildinfo" "kode.naiv.no/olemd/forgejo-mcp-broker/internal/config" "kode.naiv.no/olemd/forgejo-mcp-broker/internal/httpserver" brokerlog "kode.naiv.no/olemd/forgejo-mcp-broker/internal/log" "kode.naiv.no/olemd/forgejo-mcp-broker/internal/store" ) // Exit codes follow the usual convention: 0 success, 2 config/usage, 1 runtime. const ( exitSuccess = 0 exitRuntime = 1 exitConfig = 2 ) func main() { os.Exit(run(os.Args[1:], os.Stderr)) } // run is the testable entry point. Parses config, wires dependencies, and // blocks until the HTTP server exits or a shutdown signal arrives. Returns // an OS exit code. func run(args []string, out io.Writer) int { // --version is handled before config.Load so operators can inspect a // binary without providing the rest of the required config. for _, a := range args { if a == "--version" || a == "-version" { fmt.Fprintf(out, "fjmcp-broker %s (rev %s, built %s)\n", buildinfo.Version, buildinfo.GitRevision, buildinfo.BuildDate) return exitSuccess } } cfg, err := config.Load(args, out) switch { case errors.Is(err, flag.ErrHelp): return exitSuccess case err != nil: fmt.Fprintln(out, "fjmcp-broker: config error:") fmt.Fprintln(out, err.Error()) return exitConfig } logger := brokerlog.New(out, cfg.Debug) logger.Info("starting broker", "listen", cfg.Listen, "public_url", cfg.PublicURL, "forgejo_url", cfg.ForgejoURL, "store_path", cfg.StorePath, "max_sessions", cfg.MaxSessions, "idle_timeout", cfg.SessionIdleTimeout.String(), ) // Signal handling is owned here; the HTTP server just responds to ctx // cancellation. This keeps internal/httpserver free of signal coupling // and makes it testable without any OS-level wiring. ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) defer stop() st, err := store.Open(ctx, cfg.StorePath) if err != nil { logger.Error("open store", "err", err.Error()) return exitRuntime } defer func() { if err := st.Close(); err != nil { logger.Error("close store", "err", err.Error()) } }() srv := &httpserver.Server{ Addr: cfg.Listen, Log: logger, Store: st, } if err := srv.Run(ctx); err != nil { logger.Error("server exit", "err", err.Error()) return exitRuntime } logger.Info("broker stopped") return exitSuccess }