registry/cmd/registry/main.go
Radoslav Dimitrov 2a80690063
Introduce a /version API (#667)
<!-- Provide a brief summary of your changes -->

## Motivation and Context
<!-- Why is this change needed? What problem does it solve? -->
The following PR introduces a `/version` API that serves the build
details along with the version of the API server being served. The
`/version` endpoint allows users to programmatically query which exact
version of the registry is running in production for debugging.
~~auditing, and compatibility checks~~.

## How Has This Been Tested?
<!-- Have you tested this in a real application? Which scenarios were
tested? -->

## Breaking Changes
<!-- Will users need to update their code or configurations? -->

## Types of changes
<!-- What types of changes does your code introduce? Put an `x` in all
the boxes that apply: -->
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update

## Checklist
<!-- Go over all the following points, and put an `x` in all the boxes
that apply. -->
- [ ] I have read the [MCP
Documentation](https://modelcontextprotocol.io)
- [ ] My code follows the repository's style guidelines
- [ ] New and existing tests pass locally
- [ ] I have added appropriate error handling
- [ ] I have added or updated documentation as needed

## Additional context
<!-- Add any other context, implementation notes, or design decisions
-->

Signed-off-by: Radoslav Dimitrov <radoslav@stacklok.com>
2025-10-15 17:24:46 +03:00

141 lines
3.7 KiB
Go

package main
import (
"context"
"errors"
"flag"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"github.com/modelcontextprotocol/registry/internal/api"
v0 "github.com/modelcontextprotocol/registry/internal/api/handlers/v0"
"github.com/modelcontextprotocol/registry/internal/config"
"github.com/modelcontextprotocol/registry/internal/database"
"github.com/modelcontextprotocol/registry/internal/importer"
"github.com/modelcontextprotocol/registry/internal/service"
"github.com/modelcontextprotocol/registry/internal/telemetry"
)
// Version info for the MCP Registry application
// These variables are injected at build time via ldflags
var (
// Version is the current version of the MCP Registry application
Version = "dev"
// BuildTime is the time at which the binary was built
BuildTime = "unknown"
// GitCommit is the git commit that was compiled
GitCommit = "unknown"
)
func main() {
// Parse command line flags
showVersion := flag.Bool("version", false, "Display version information")
flag.Parse()
// Show version information if requested
if *showVersion {
log.Printf("MCP Registry %s\n", Version)
log.Printf("Git commit: %s\n", GitCommit)
log.Printf("Build time: %s\n", BuildTime)
return
}
log.Printf("Starting MCP Registry Application v%s (commit: %s)", Version, GitCommit)
var (
registryService service.RegistryService
db database.Database
err error
)
// Initialize configuration
cfg := config.NewConfig()
// Create a context with timeout for PostgreSQL connection
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
// Connect to PostgreSQL
db, err = database.NewPostgreSQL(ctx, cfg.DatabaseURL)
if err != nil {
log.Printf("Failed to connect to PostgreSQL: %v", err)
return
}
// Store the PostgreSQL instance for later cleanup
defer func() {
if err := db.Close(); err != nil {
log.Printf("Error closing PostgreSQL connection: %v", err)
} else {
log.Println("PostgreSQL connection closed successfully")
}
}()
registryService = service.NewRegistryService(db, cfg)
// Import seed data if seed source is provided
if cfg.SeedFrom != "" {
log.Printf("Importing data from %s...", cfg.SeedFrom)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()
importerService := importer.NewService(registryService)
if err := importerService.ImportFromPath(ctx, cfg.SeedFrom); err != nil {
log.Printf("Failed to import seed data: %v", err)
}
}
shutdownTelemetry, metrics, err := telemetry.InitMetrics(cfg.Version)
if err != nil {
log.Printf("Failed to initialize metrics: %v", err)
return
}
defer func() {
if err := shutdownTelemetry(context.Background()); err != nil {
log.Printf("Failed to shutdown telemetry: %v", err)
}
}()
// Prepare version information
versionInfo := &v0.VersionBody{
Version: Version,
GitCommit: GitCommit,
BuildTime: BuildTime,
}
// Initialize HTTP server
server := api.NewServer(cfg, registryService, metrics, versionInfo)
// Start server in a goroutine so it doesn't block signal handling
go func() {
if err := server.Start(); err != nil && !errors.Is(err, http.ErrServerClosed) {
log.Printf("Failed to start server: %v", err)
os.Exit(1)
}
}()
// Wait for interrupt signal to gracefully shutdown the server
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("Shutting down server...")
// Create context with timeout for shutdown
sctx, scancel := context.WithTimeout(context.Background(), 10*time.Second)
defer scancel()
// Gracefully shutdown the server
if err := server.Shutdown(sctx); err != nil {
log.Printf("Server forced to shutdown: %v", err)
}
log.Println("Server exiting")
}