registry/internal/api/server.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

78 lines
2.2 KiB
Go

package api
import (
"context"
"log"
"net/http"
"strings"
"time"
"github.com/danielgtaylor/huma/v2"
v0 "github.com/modelcontextprotocol/registry/internal/api/handlers/v0"
"github.com/modelcontextprotocol/registry/internal/api/router"
"github.com/modelcontextprotocol/registry/internal/config"
"github.com/modelcontextprotocol/registry/internal/service"
"github.com/modelcontextprotocol/registry/internal/telemetry"
)
// TrailingSlashMiddleware redirects requests with trailing slashes to their canonical form
func TrailingSlashMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Only redirect if the path is not "/" and ends with a "/"
if r.URL.Path != "/" && strings.HasSuffix(r.URL.Path, "/") {
// Create a copy of the URL and remove the trailing slash
newURL := *r.URL
newURL.Path = strings.TrimSuffix(r.URL.Path, "/")
// Use 308 Permanent Redirect to preserve the request method
http.Redirect(w, r, newURL.String(), http.StatusPermanentRedirect)
return
}
next.ServeHTTP(w, r)
})
}
// Server represents the HTTP server
type Server struct {
config *config.Config
registry service.RegistryService
humaAPI huma.API
server *http.Server
}
// NewServer creates a new HTTP server
func NewServer(cfg *config.Config, registryService service.RegistryService, metrics *telemetry.Metrics, versionInfo *v0.VersionBody) *Server {
// Create HTTP mux and Huma API
mux := http.NewServeMux()
api := router.NewHumaAPI(cfg, registryService, mux, metrics, versionInfo)
// Wrap the mux with trailing slash middleware
handler := TrailingSlashMiddleware(mux)
server := &Server{
config: cfg,
registry: registryService,
humaAPI: api,
server: &http.Server{
Addr: cfg.ServerAddress,
Handler: handler,
ReadHeaderTimeout: 10 * time.Second,
},
}
return server
}
// Start begins listening for incoming HTTP requests
func (s *Server) Start() error {
log.Printf("HTTP server starting on %s", s.config.ServerAddress)
return s.server.ListenAndServe()
}
// Shutdown gracefully shuts down the server
func (s *Server) Shutdown(ctx context.Context) error {
return s.server.Shutdown(ctx)
}