api: Use Huma to generate swagger docs and parse JSON automatically (#262)

## Motivation and Context

Currently there's a lot of unsafe manual JSON unmarshalling/marshalling,
and we have to manually keep the openapi schema in sync.

This PR migrates the API to
[Huma](https://github.com/danielgtaylor/huma), which appears like a
popular, well-maintained way to build APIs in Golang. I'm not a Golang
expert, but seemed highly recommended by Reddit + Claude and has a
decent star count. It provides automatic openapi spec generation and
docs hosting, JSON parsing, as well as the ability to add auth
middlewares etc.

## How Has This Been Tested?

Running locally, and poking at the endpoints

## Breaking Changes

Docs are served at `/docs` rather than `/v0/swagger/index.html` now

## 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)
- [x] New feature (non-breaking change which adds functionality)
- [x] Breaking change (fix or feature that would cause existing
functionality to change)
- [x] Documentation update

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

## Additional context
<!-- Add any other context, implementation notes, or design decisions
-->
This commit is contained in:
adam jones 2025-08-13 16:57:55 +01:00 • committed by GitHub
commit 6b84493ca9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 1622 additions and 2267 deletions

View file

@ -4,22 +4,35 @@ package router
import (
"net/http"
"github.com/danielgtaylor/huma/v2"
"github.com/danielgtaylor/huma/v2/adapters/humago"
"github.com/modelcontextprotocol/registry/internal/auth"
"github.com/modelcontextprotocol/registry/internal/config"
"github.com/modelcontextprotocol/registry/internal/service"
)
// New creates a new router with all API versions registered
func New(cfg *config.Config, registry service.RegistryService, authService auth.Service) *http.ServeMux {
mux := http.NewServeMux()
// NewHumaAPI creates a new Huma API with all routes registered
//
//nolint:ireturn // huma.API is the expected interface type for Huma APIs
func NewHumaAPI(cfg *config.Config, registry service.RegistryService, authService auth.Service, mux *http.ServeMux) huma.API {
// Create Huma API configuration
humaConfig := huma.DefaultConfig("MCP Registry API", "1.0.0")
humaConfig.Info.Description = "A community driven registry service for Model Context Protocol (MCP) servers."
// Disable $schema property in responses: https://github.com/danielgtaylor/huma/issues/230
humaConfig.CreateHooks = []func(huma.Config) huma.Config{}
// Create a new API using humago adapter for standard library
api := humago.New(mux, humaConfig)
// Register routes for all API versions
RegisterV0Routes(mux, cfg, registry, authService)
RegisterV0Routes(api, cfg, registry, authService)
// Redirect root to Swagger UI
// Add redirect from / to /docs
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/v0/swagger/index.html", http.StatusFound)
if r.URL.Path == "/" {
http.Redirect(w, r, "/docs", http.StatusMovedPermanently)
}
})
return mux
return api
}

View file

@ -2,26 +2,19 @@
package router
import (
"net/http"
"github.com/danielgtaylor/huma/v2"
v0 "github.com/modelcontextprotocol/registry/internal/api/handlers/v0"
"github.com/modelcontextprotocol/registry/internal/auth"
"github.com/modelcontextprotocol/registry/internal/config"
"github.com/modelcontextprotocol/registry/internal/service"
)
// RegisterV0Routes registers all v0 API routes to the provided router
func RegisterV0Routes(
mux *http.ServeMux, cfg *config.Config, registry service.RegistryService, authService auth.Service,
api huma.API, cfg *config.Config, registry service.RegistryService, authService auth.Service,
) {
// Register v0 endpoints
mux.HandleFunc("/v0/health", v0.HealthHandler(cfg))
mux.HandleFunc("/v0/servers", v0.ServersHandler(registry))
mux.HandleFunc("/v0/servers/{id}", v0.ServersDetailHandler(registry))
mux.HandleFunc("/v0/ping", v0.PingHandler(cfg))
mux.HandleFunc("/v0/publish", v0.PublishHandler(registry, authService))
// Register Swagger UI routes
mux.HandleFunc("/v0/swagger/", v0.SwaggerHandler())
mux.HandleFunc("/v0/swagger/doc.json", v0.SwaggerJSONHandler())
v0.RegisterHealthEndpoint(api, cfg)
v0.RegisterPingEndpoint(api)
v0.RegisterServersEndpoints(api, registry)
// v0.RegisterAuthEndpoints(api, authService)
v0.RegisterPublishEndpoint(api, registry, authService)
}