package main import ( "context" "flag" "fmt" "log" "net/http" "os" "os/signal" "syscall" "git.georgelambert.org/marchon/verae-mcp-timestamping-server-go/internal/edgebill" "git.georgelambert.org/marchon/verae-mcp-timestamping-server-go/internal/embednats" "git.georgelambert.org/marchon/verae-mcp-timestamping-server-go/internal/ledger" "git.georgelambert.org/marchon/verae-mcp-timestamping-server-go/internal/server" "git.georgelambert.org/marchon/verae-mcp-timestamping-server-go/internal/tools" "git.georgelambert.org/marchon/verae-mcp-timestamping-server-go/internal/veraetime" "github.com/modelcontextprotocol/go-sdk/mcp" ) func main() { transport := flag.String("transport", "streamable-http", "MCP transport: streamable-http or stdio") host := flag.String("host", "127.0.0.1", "HTTP bind host") port := flag.Int("port", 8000, "HTTP bind port (shadow often uses 8001)") flag.Parse() cfg := server.LoadConfig(*transport, *host, *port) led, err := ledger.Open(cfg.LedgerPath) if err != nil { log.Fatalf("ledger: %v", err) } global := veraetime.NewClient(cfg.GlobalURL, veraetime.WithAuthPath(cfg.AuthPath)) keeper := veraetime.NewKeeper(global, cfg.AuthPath) edge := edgebill.FromEnv() if edge != nil { log.Printf("MCP global stamps bill via edge %s (channel=%s)", edge.BaseURL, edge.Channel) } emb, err := embednats.Start(embednats.Options{ Listen: cfg.NATSListen, Ledger: led, Global: global, Keeper: keeper, RequireBearer: cfg.RequireBearer, }) if err != nil { log.Fatalf("embednats: %v", err) } if emb != nil { defer emb.Shutdown() log.Printf("embedded NATS listening on %s", emb.ClientURL()) } switch cfg.Transport { case "streamable-http", "http": srv, err := server.New(server.Options{ Ledger: led, Global: global, Keeper: keeper, Edge: edge, AuthPath: cfg.AuthPath, GlobalURL: cfg.GlobalURL, RequireBearer: cfg.RequireBearer, // Stateful sessions + JSON responses match public mcp.verae.ai clients // (Mcp-Session-Id after initialize). Stateless broke tools/call for those clients. JSONResponse: true, Stateless: false, // DEC-654: the HTTP transport is the public server (mcp.verae.ai) unless VERAE_MCP_TRUSTED_LOCAL=1. Public: os.Getenv("VERAE_MCP_TRUSTED_LOCAL") != "1", }) if err != nil { log.Fatalf("server: %v", err) } handler := server.WithCORS(srv.Handler(), cfg.CORS) httpSrv := &http.Server{Addr: cfg.Addr(), Handler: handler} go func() { log.Printf("verae-mcp-timestamping-server listening on http://%s/mcp", cfg.Addr()) if err := httpSrv.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Fatalf("listen: %v", err) } }() waitSignal() _ = httpSrv.Shutdown(context.Background()) case "stdio": mcpServer := tools.NewServer(&tools.Deps{ Ledger: led, Global: global, Keeper: keeper, Edge: edge, RequireBearer: cfg.RequireBearer, }) ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) defer stop() if err := mcpServer.Run(ctx, &mcp.StdioTransport{}); err != nil { fmt.Fprintf(os.Stderr, "stdio: %v\n", err) os.Exit(1) } default: log.Fatalf("unknown transport %q (use streamable-http or stdio)", cfg.Transport) } } func waitSignal() { ch := make(chan os.Signal, 1) signal.Notify(ch, os.Interrupt, syscall.SIGTERM) <-ch }