diff --git a/docs/guides/consuming/use-rest-api.md b/docs/guides/consuming/use-rest-api.md index 02e4fff..71942da 100644 --- a/docs/guides/consuming/use-rest-api.md +++ b/docs/guides/consuming/use-rest-api.md @@ -4,13 +4,20 @@ Integration patterns and best practices for building applications that consume M ## Key details -**Base URL**: `https://registry.modelcontextprotocol.io` +**Base URL**: `https://registry.modelcontextprotocol.io` **Authentication**: Not required for read-only access -- **`GET /v0/servers`** - List all servers with pagination -- **`GET /v0/servers/{serverName}/versions`** - List all versions of a server -- **`GET /v0/servers/{serverName}/versions/{version}`** - Get specific version of server. Use the special version `latest` to get the latest version. +**API Versions**: +- `/v0/` - Development version with latest features (may receive additive changes) +- `/v0.1/` - Stable version (only backward-compatible changes) + +**Recommendation**: Use `/v0.1/` for production applications requiring API stability. Both versions currently have identical functionality. + +**Core endpoints:** +- **`GET /v0.1/servers`** - List all servers with pagination +- **`GET /v0.1/servers/{serverName}/versions`** - List all versions of a server +- **`GET /v0.1/servers/{serverName}/versions/{version}`** - Get specific version of server. Use the special version `latest` to get the latest version. Server names and version strings should be URL-encoded in paths. diff --git a/docs/reference/api/CHANGELOG.md b/docs/reference/api/CHANGELOG.md index 03464ca..39c8eb2 100644 --- a/docs/reference/api/CHANGELOG.md +++ b/docs/reference/api/CHANGELOG.md @@ -4,6 +4,24 @@ Changes to the REST API endpoints and responses. ## Unreleased +### Added + +#### API Versioning - v0.1 Introduction + +Introduced `/v0.1/` as a stable API version while `/v0/` continues as the development version. + +**New version paths:** +- All `/v0/` endpoints are now also available at `/v0.1/` +- Both versions currently share identical behavior +- `/v0/` will continue to evolve with additive changes (new optional fields, new endpoints) +- `/v0.1/` will remain stable with only additive, backward-compatible changes +- Both versions will be maintained until a future v1.0 release + +**Migration guidance:** +- Production applications should consider using `/v0.1/` for stability +- Development and testing can continue using `/v0/` for latest features +- No immediate action required - `/v0/` remains fully supported + ### ⚠️ BREAKING CHANGES #### Endpoint Simplification diff --git a/internal/api/handlers/v0/auth/dns.go b/internal/api/handlers/v0/auth/dns.go index 99c7a3c..93a435a 100644 --- a/internal/api/handlers/v0/auth/dns.go +++ b/internal/api/handlers/v0/auth/dns.go @@ -50,14 +50,14 @@ func (h *DNSAuthHandler) SetResolver(resolver DNSResolver) { } // RegisterDNSEndpoint registers the DNS authentication endpoint -func RegisterDNSEndpoint(api huma.API, cfg *config.Config) { +func RegisterDNSEndpoint(api huma.API, pathPrefix string, cfg *config.Config) { handler := NewDNSAuthHandler(cfg) // DNS authentication endpoint huma.Register(api, huma.Operation{ - OperationID: "exchange-dns-token", + OperationID: "exchange-dns-token" + pathPrefix, Method: http.MethodPost, - Path: "/v0/auth/dns", + Path: pathPrefix + "/auth/dns", Summary: "Exchange DNS signature for Registry JWT", Description: "Authenticate using DNS TXT record public key and signed timestamp", Tags: []string{"auth"}, diff --git a/internal/api/handlers/v0/auth/github_at.go b/internal/api/handlers/v0/auth/github_at.go index c1d0e64..2f060a1 100644 --- a/internal/api/handlers/v0/auth/github_at.go +++ b/internal/api/handlers/v0/auth/github_at.go @@ -42,15 +42,15 @@ func (h *GitHubHandler) SetBaseURL(url string) { h.baseURL = url } -// RegisterGitHubATEndpoint registers the GitHub access token authentication endpoint -func RegisterGitHubATEndpoint(api huma.API, cfg *config.Config) { +// RegisterGitHubATEndpoint registers the GitHub access token authentication endpoint with a custom path prefix +func RegisterGitHubATEndpoint(api huma.API, pathPrefix string, cfg *config.Config) { handler := NewGitHubHandler(cfg) // GitHub token exchange endpoint huma.Register(api, huma.Operation{ - OperationID: "exchange-github-token", + OperationID: "exchange-github-token" + pathPrefix, Method: http.MethodPost, - Path: "/v0/auth/github-at", + Path: pathPrefix + "/auth/github-at", Summary: "Exchange GitHub OAuth access token for Registry JWT", Description: "Exchange a GitHub OAuth access token for a short-lived Registry JWT token", Tags: []string{"auth"}, diff --git a/internal/api/handlers/v0/auth/github_oidc.go b/internal/api/handlers/v0/auth/github_oidc.go index 9deb89c..f935b96 100644 --- a/internal/api/handlers/v0/auth/github_oidc.go +++ b/internal/api/handlers/v0/auth/github_oidc.go @@ -226,14 +226,14 @@ func (h *GitHubOIDCHandler) SetValidator(validator OIDCValidator) { } // RegisterGitHubOIDCEndpoint registers the GitHub OIDC authentication endpoint -func RegisterGitHubOIDCEndpoint(api huma.API, cfg *config.Config) { +func RegisterGitHubOIDCEndpoint(api huma.API, pathPrefix string, cfg *config.Config) { handler := NewGitHubOIDCHandler(cfg) // GitHub OIDC token exchange endpoint huma.Register(api, huma.Operation{ - OperationID: "exchange-github-oidc-token", + OperationID: "exchange-github-oidc-token" + pathPrefix, Method: http.MethodPost, - Path: "/v0/auth/github-oidc", + Path: pathPrefix + "/auth/github-oidc", Summary: "Exchange GitHub OIDC token for Registry JWT", Description: "Exchange a GitHub Actions OIDC token for a short-lived Registry JWT token", Tags: []string{"auth"}, diff --git a/internal/api/handlers/v0/auth/http.go b/internal/api/handlers/v0/auth/http.go index d7acebf..f397e7d 100644 --- a/internal/api/handlers/v0/auth/http.go +++ b/internal/api/handlers/v0/auth/http.go @@ -108,14 +108,14 @@ func (h *HTTPAuthHandler) SetFetcher(fetcher HTTPKeyFetcher) { } // RegisterHTTPEndpoint registers the HTTP authentication endpoint -func RegisterHTTPEndpoint(api huma.API, cfg *config.Config) { +func RegisterHTTPEndpoint(api huma.API, pathPrefix string, cfg *config.Config) { handler := NewHTTPAuthHandler(cfg) // HTTP authentication endpoint huma.Register(api, huma.Operation{ - OperationID: "exchange-http-token", + OperationID: "exchange-http-token" + pathPrefix, Method: http.MethodPost, - Path: "/v0/auth/http", + Path: pathPrefix + "/auth/http", Summary: "Exchange HTTP signature for Registry JWT", Description: "Authenticate using HTTP-hosted public key and signed timestamp", Tags: []string{"auth"}, diff --git a/internal/api/handlers/v0/auth/main.go b/internal/api/handlers/v0/auth/main.go index c450c77..26a566b 100644 --- a/internal/api/handlers/v0/auth/main.go +++ b/internal/api/handlers/v0/auth/main.go @@ -5,23 +5,23 @@ import ( "github.com/modelcontextprotocol/registry/internal/config" ) -// RegisterAuthEndpoints registers all authentication endpoints -func RegisterAuthEndpoints(api huma.API, cfg *config.Config) { +// RegisterAuthEndpoints registers all authentication endpoints with a custom path prefix +func RegisterAuthEndpoints(api huma.API, pathPrefix string, cfg *config.Config) { // Register GitHub access token authentication endpoint - RegisterGitHubATEndpoint(api, cfg) + RegisterGitHubATEndpoint(api, pathPrefix, cfg) // Register GitHub OIDC authentication endpoint - RegisterGitHubOIDCEndpoint(api, cfg) + RegisterGitHubOIDCEndpoint(api, pathPrefix, cfg) // Register configurable OIDC authentication endpoints - RegisterOIDCEndpoints(api, cfg) + RegisterOIDCEndpoints(api, pathPrefix, cfg) // Register DNS-based authentication endpoint - RegisterDNSEndpoint(api, cfg) + RegisterDNSEndpoint(api, pathPrefix, cfg) // Register HTTP-based authentication endpoint - RegisterHTTPEndpoint(api, cfg) + RegisterHTTPEndpoint(api, pathPrefix, cfg) // Register anonymous authentication endpoint - RegisterNoneEndpoint(api, cfg) + RegisterNoneEndpoint(api, pathPrefix, cfg) } diff --git a/internal/api/handlers/v0/auth/none.go b/internal/api/handlers/v0/auth/none.go index dd09a34..b743121 100644 --- a/internal/api/handlers/v0/auth/none.go +++ b/internal/api/handlers/v0/auth/none.go @@ -28,7 +28,7 @@ func NewNoneHandler(cfg *config.Config) *NoneHandler { // RegisterNoneEndpoint registers the anonymous authentication endpoint // WARNING: This endpoint is intended for local development and automated tests only. // It should NOT be enabled in production environments as it bypasses normal authentication. -func RegisterNoneEndpoint(api huma.API, cfg *config.Config) { +func RegisterNoneEndpoint(api huma.API, pathPrefix string, cfg *config.Config) { if !cfg.EnableAnonymousAuth { return } @@ -39,7 +39,7 @@ func RegisterNoneEndpoint(api huma.API, cfg *config.Config) { huma.Register(api, huma.Operation{ OperationID: "get-anonymous-token", Method: http.MethodPost, - Path: "/v0/auth/none", + Path: pathPrefix + "/auth/none", Summary: "Get anonymous Registry JWT (Development/Testing Only)", Description: "Get a short-lived Registry JWT token for publishing and editing servers in the io.modelcontextprotocol.anonymous/* namespace. This endpoint is intended for local development and automated testing only.", Tags: []string{"auth"}, diff --git a/internal/api/handlers/v0/auth/oidc.go b/internal/api/handlers/v0/auth/oidc.go index 3a0ba8c..780a13e 100644 --- a/internal/api/handlers/v0/auth/oidc.go +++ b/internal/api/handlers/v0/auth/oidc.go @@ -145,7 +145,7 @@ func (h *OIDCHandler) SetValidator(validator GenericOIDCValidator) { } // RegisterOIDCEndpoints registers all OIDC authentication endpoints -func RegisterOIDCEndpoints(api huma.API, cfg *config.Config) { +func RegisterOIDCEndpoints(api huma.API, pathPrefix string, cfg *config.Config) { if !cfg.OIDCEnabled { return // Skip registration if OIDC is not enabled } @@ -154,9 +154,9 @@ func RegisterOIDCEndpoints(api huma.API, cfg *config.Config) { // Direct token exchange endpoint huma.Register(api, huma.Operation{ - OperationID: "exchange-oidc-token", + OperationID: "exchange-oidc-token" + pathPrefix, Method: http.MethodPost, - Path: "/v0/auth/oidc", + Path: pathPrefix + "/auth/oidc", Summary: "Exchange OIDC ID token for Registry JWT", Description: "Exchange an OIDC ID token from any configured provider for a short-lived Registry JWT token", Tags: []string{"auth"}, diff --git a/internal/api/handlers/v0/edit.go b/internal/api/handlers/v0/edit.go index ee7d8af..46e048a 100644 --- a/internal/api/handlers/v0/edit.go +++ b/internal/api/handlers/v0/edit.go @@ -25,15 +25,15 @@ type EditServerInput struct { Body apiv0.ServerJSON `body:""` } -// RegisterEditEndpoints registers the edit endpoint -func RegisterEditEndpoints(api huma.API, registry service.RegistryService, cfg *config.Config) { +// RegisterEditEndpoints registers the edit endpoint with a custom path prefix +func RegisterEditEndpoints(api huma.API, pathPrefix string, registry service.RegistryService, cfg *config.Config) { jwtManager := auth.NewJWTManager(cfg) // Edit server endpoint huma.Register(api, huma.Operation{ - OperationID: "edit-server", + OperationID: "edit-server" + pathPrefix, Method: http.MethodPut, - Path: "/v0/servers/{serverName}/versions/{version}", + Path: pathPrefix + "/servers/{serverName}/versions/{version}", Summary: "Edit MCP server", Description: "Update a specific version of an existing MCP server (admin only).", Tags: []string{"admin"}, diff --git a/internal/api/handlers/v0/edit_test.go b/internal/api/handlers/v0/edit_test.go index 59779c9..87e82eb 100644 --- a/internal/api/handlers/v0/edit_test.go +++ b/internal/api/handlers/v0/edit_test.go @@ -371,7 +371,7 @@ func TestEditServerEndpoint(t *testing.T) { api := humago.New(mux, huma.DefaultConfig("Test API", "1.0.0")) // Register edit endpoints - v0.RegisterEditEndpoints(api, registryService, cfg) + v0.RegisterEditEndpoints(api, "/v0", registryService, cfg) // Create request body requestBody, err := json.Marshal(tc.requestBody) @@ -469,7 +469,7 @@ func TestEditServerEndpointEdgeCases(t *testing.T) { // Create API mux := http.NewServeMux() api := humago.New(mux, huma.DefaultConfig("Test API", "1.0.0")) - v0.RegisterEditEndpoints(api, registryService, cfg) + v0.RegisterEditEndpoints(api, "/v0", registryService, cfg) t.Run("status transitions", func(t *testing.T) { tests := []struct { diff --git a/internal/api/handlers/v0/health.go b/internal/api/handlers/v0/health.go index ea2060a..8fdf0ec 100644 --- a/internal/api/handlers/v0/health.go +++ b/internal/api/handlers/v0/health.go @@ -18,18 +18,18 @@ type HealthBody struct { GitHubClientID string `json:"github_client_id,omitempty" doc:"GitHub OAuth App Client ID"` } -// RegisterHealthEndpoint registers the health check endpoint -func RegisterHealthEndpoint(api huma.API, cfg *config.Config, metrics *telemetry.Metrics) { +// RegisterHealthEndpoint registers the health check endpoint with a custom path prefix +func RegisterHealthEndpoint(api huma.API, pathPrefix string, cfg *config.Config, metrics *telemetry.Metrics) { huma.Register(api, huma.Operation{ - OperationID: "get-health", + OperationID: "get-health" + pathPrefix, Method: http.MethodGet, - Path: "/v0/health", + Path: pathPrefix + "/health", Summary: "Health check", Description: "Check the health status of the API", Tags: []string{"health"}, }, func(ctx context.Context, _ *struct{}) (*Response[HealthBody], error) { // Record the health check metrics - recordHealthMetrics(ctx, metrics, "/v0/health", cfg.Version) + recordHealthMetrics(ctx, metrics, pathPrefix+"/health", cfg.Version) return &Response[HealthBody]{ Body: HealthBody{ diff --git a/internal/api/handlers/v0/health_test.go b/internal/api/handlers/v0/health_test.go index 1de394e..be7ac98 100644 --- a/internal/api/handlers/v0/health_test.go +++ b/internal/api/handlers/v0/health_test.go @@ -56,7 +56,7 @@ func TestHealthEndpoint(t *testing.T) { shutdownTelemetry, metrics, _ := telemetry.InitMetrics("test") // Register the health endpoint - v0.RegisterHealthEndpoint(api, tc.config, metrics) + v0.RegisterHealthEndpoint(api, "/v0", tc.config, metrics) // Create a test request req := httptest.NewRequest(http.MethodGet, "/v0/health", nil) diff --git a/internal/api/handlers/v0/ping.go b/internal/api/handlers/v0/ping.go index 6c7b9b2..4274609 100644 --- a/internal/api/handlers/v0/ping.go +++ b/internal/api/handlers/v0/ping.go @@ -12,12 +12,12 @@ type PingBody struct { Pong bool `json:"pong" example:"true" doc:"Ping response"` } -// RegisterPingEndpoint registers the ping endpoint -func RegisterPingEndpoint(api huma.API) { +// RegisterPingEndpoint registers the ping endpoint with a custom path prefix +func RegisterPingEndpoint(api huma.API, pathPrefix string) { huma.Register(api, huma.Operation{ - OperationID: "ping", + OperationID: "ping" + pathPrefix, Method: http.MethodGet, - Path: "/v0/ping", + Path: pathPrefix + "/ping", Summary: "Ping", Description: "Simple ping endpoint", Tags: []string{"ping"}, diff --git a/internal/api/handlers/v0/ping_test.go b/internal/api/handlers/v0/ping_test.go index 94ee31e..33a4a84 100644 --- a/internal/api/handlers/v0/ping_test.go +++ b/internal/api/handlers/v0/ping_test.go @@ -17,7 +17,7 @@ func TestPingEndpoint(t *testing.T) { api := humago.New(mux, huma.DefaultConfig("Test API", "1.0.0")) // Register the ping endpoint - v0.RegisterPingEndpoint(api) + v0.RegisterPingEndpoint(api, "/v0") // Create a test request req := httptest.NewRequest(http.MethodGet, "/v0/ping", nil) diff --git a/internal/api/handlers/v0/publish.go b/internal/api/handlers/v0/publish.go index 60348e5..97ed122 100644 --- a/internal/api/handlers/v0/publish.go +++ b/internal/api/handlers/v0/publish.go @@ -18,15 +18,15 @@ type PublishServerInput struct { Body apiv0.ServerJSON `body:""` } -// RegisterPublishEndpoint registers the publish endpoint -func RegisterPublishEndpoint(api huma.API, registry service.RegistryService, cfg *config.Config) { +// RegisterPublishEndpoint registers the publish endpoint with a custom path prefix +func RegisterPublishEndpoint(api huma.API, pathPrefix string, registry service.RegistryService, cfg *config.Config) { // Create JWT manager for token validation jwtManager := auth.NewJWTManager(cfg) huma.Register(api, huma.Operation{ - OperationID: "publish-server", + OperationID: "publish-server" + pathPrefix, Method: http.MethodPost, - Path: "/v0/publish", + Path: pathPrefix + "/publish", Summary: "Publish MCP server", Description: "Publish a new MCP server to the registry or update an existing one", Tags: []string{"publish"}, diff --git a/internal/api/handlers/v0/publish_integration_test.go b/internal/api/handlers/v0/publish_integration_test.go index a8d317a..540d211 100644 --- a/internal/api/handlers/v0/publish_integration_test.go +++ b/internal/api/handlers/v0/publish_integration_test.go @@ -53,7 +53,7 @@ func TestPublishIntegration(t *testing.T) { api := humago.New(mux, huma.DefaultConfig("Test API", "1.0.0")) // Register the endpoint - v0.RegisterPublishEndpoint(api, registryService, testConfig) + v0.RegisterPublishEndpoint(api, "/v0", registryService, testConfig) t.Run("successful publish with GitHub auth", func(t *testing.T) { publishReq := apiv0.ServerJSON{ diff --git a/internal/api/handlers/v0/publish_registry_validation_test.go b/internal/api/handlers/v0/publish_registry_validation_test.go index 01a61dd..ccad630 100644 --- a/internal/api/handlers/v0/publish_registry_validation_test.go +++ b/internal/api/handlers/v0/publish_registry_validation_test.go @@ -41,7 +41,7 @@ func TestPublishRegistryValidation(t *testing.T) { api := humago.New(mux, huma.DefaultConfig("Test API", "1.0.0")) // Register the endpoint - v0.RegisterPublishEndpoint(api, registryService, testConfig) + v0.RegisterPublishEndpoint(api, "/v0", registryService, testConfig) t.Run("publish fails with npm registry validation error", func(t *testing.T) { publishReq := apiv0.ServerJSON{ diff --git a/internal/api/handlers/v0/publish_test.go b/internal/api/handlers/v0/publish_test.go index f04510e..9efeba3 100644 --- a/internal/api/handlers/v0/publish_test.go +++ b/internal/api/handlers/v0/publish_test.go @@ -381,7 +381,7 @@ func TestPublishEndpoint(t *testing.T) { api := humago.New(mux, huma.DefaultConfig("Test API", "1.0.0")) // Register the endpoint with test config - v0.RegisterPublishEndpoint(api, registryService, testConfig) + v0.RegisterPublishEndpoint(api, "/v0", registryService, testConfig) // Prepare request body var requestBody []byte @@ -480,7 +480,7 @@ func TestPublishEndpoint_MultipleSlashesEdgeCases(t *testing.T) { api := humago.New(mux, huma.DefaultConfig("Test API", "1.0.0")) // Register the endpoint - v0.RegisterPublishEndpoint(api, registryService, testConfig) + v0.RegisterPublishEndpoint(api, "/v0", registryService, testConfig) // Create request body requestBody := apiv0.ServerJSON{ diff --git a/internal/api/handlers/v0/servers.go b/internal/api/handlers/v0/servers.go index ecc2a9b..f9c02d7 100644 --- a/internal/api/handlers/v0/servers.go +++ b/internal/api/handlers/v0/servers.go @@ -40,13 +40,13 @@ type ServerVersionsInput struct { ServerName string `path:"serverName" doc:"URL-encoded server name" example:"com.example%2Fmy-server"` } -// RegisterServersEndpoints registers all server-related endpoints -func RegisterServersEndpoints(api huma.API, registry service.RegistryService) { +// RegisterServersEndpoints registers all server-related endpoints with a custom path prefix +func RegisterServersEndpoints(api huma.API, pathPrefix string, registry service.RegistryService) { // List servers endpoint huma.Register(api, huma.Operation{ - OperationID: "list-servers", + OperationID: "list-servers" + pathPrefix, Method: http.MethodGet, - Path: "/v0/servers", + Path: pathPrefix + "/servers", Summary: "List MCP servers", Description: "Get a paginated list of MCP servers from the registry", Tags: []string{"servers"}, @@ -106,9 +106,9 @@ func RegisterServersEndpoints(api huma.API, registry service.RegistryService) { // Get specific server version endpoint (supports "latest" as special version) huma.Register(api, huma.Operation{ - OperationID: "get-server-version", + OperationID: "get-server-version" + pathPrefix, Method: http.MethodGet, - Path: "/v0/servers/{serverName}/versions/{version}", + Path: pathPrefix + "/servers/{serverName}/versions/{version}", Summary: "Get specific MCP server version", Description: "Get detailed information about a specific version of an MCP server. Use the special version 'latest' to get the latest version.", Tags: []string{"servers"}, @@ -147,9 +147,9 @@ func RegisterServersEndpoints(api huma.API, registry service.RegistryService) { // Get server versions endpoint huma.Register(api, huma.Operation{ - OperationID: "get-server-versions", + OperationID: "get-server-versions" + pathPrefix, Method: http.MethodGet, - Path: "/v0/servers/{serverName}/versions", + Path: pathPrefix + "/servers/{serverName}/versions", Summary: "Get all versions of an MCP server", Description: "Get all available versions for a specific MCP server", Tags: []string{"servers"}, diff --git a/internal/api/handlers/v0/servers_test.go b/internal/api/handlers/v0/servers_test.go index 8dc9f66..e0d19a0 100644 --- a/internal/api/handlers/v0/servers_test.go +++ b/internal/api/handlers/v0/servers_test.go @@ -44,7 +44,7 @@ func TestListServersEndpoint(t *testing.T) { // Create API mux := http.NewServeMux() api := humago.New(mux, huma.DefaultConfig("Test API", "1.0.0")) - v0.RegisterServersEndpoints(api, registryService) + v0.RegisterServersEndpoints(api, "/v0", registryService) tests := []struct { name string @@ -130,7 +130,7 @@ func TestGetLatestServerVersionEndpoint(t *testing.T) { // Create API mux := http.NewServeMux() api := humago.New(mux, huma.DefaultConfig("Test API", "1.0.0")) - v0.RegisterServersEndpoints(api, registryService) + v0.RegisterServersEndpoints(api, "/v0", registryService) tests := []struct { name string @@ -210,7 +210,7 @@ func TestGetServerVersionEndpoint(t *testing.T) { // Create API mux := http.NewServeMux() api := humago.New(mux, huma.DefaultConfig("Test API", "1.0.0")) - v0.RegisterServersEndpoints(api, registryService) + v0.RegisterServersEndpoints(api, "/v0", registryService) tests := []struct { name string @@ -321,7 +321,7 @@ func TestGetAllVersionsEndpoint(t *testing.T) { // Create API mux := http.NewServeMux() api := humago.New(mux, huma.DefaultConfig("Test API", "1.0.0")) - v0.RegisterServersEndpoints(api, registryService) + v0.RegisterServersEndpoints(api, "/v0", registryService) tests := []struct { name string @@ -420,7 +420,7 @@ func TestServersEndpointEdgeCases(t *testing.T) { // Create API mux := http.NewServeMux() api := humago.New(mux, huma.DefaultConfig("Test API", "1.0.0")) - v0.RegisterServersEndpoints(api, registryService) + v0.RegisterServersEndpoints(api, "/v0", registryService) t.Run("URL encoding edge cases", func(t *testing.T) { tests := []struct { diff --git a/internal/api/handlers/v0/telemetry_test.go b/internal/api/handlers/v0/telemetry_test.go index e0708b4..4951b9e 100644 --- a/internal/api/handlers/v0/telemetry_test.go +++ b/internal/api/handlers/v0/telemetry_test.go @@ -46,8 +46,8 @@ func TestPrometheusHandler(t *testing.T) { api.UseMiddleware(router.MetricTelemetryMiddleware(metrics, router.WithSkipPaths("/health", "/metrics", "/ping", "/docs"), )) - v0.RegisterHealthEndpoint(api, cfg, metrics) - v0.RegisterServersEndpoints(api, registryService) + v0.RegisterHealthEndpoint(api, "/v0", cfg, metrics) + v0.RegisterServersEndpoints(api, "/v0", registryService) // Add /metrics for Prometheus metrics using promhttp mux.Handle("/metrics", metrics.PrometheusHandler()) diff --git a/internal/api/router/router.go b/internal/api/router/router.go index c592c52..0907132 100644 --- a/internal/api/router/router.go +++ b/internal/api/router/router.go @@ -102,9 +102,10 @@ func handle404(w http.ResponseWriter, r *http.Request) { detail := "Endpoint not found. See /docs for the API documentation." // Provide suggestions for common API endpoint mistakes - if !strings.HasPrefix(path, "/v0/") { + if !strings.HasPrefix(path, "/v0/") && !strings.HasPrefix(path, "/v0.1/") { detail = fmt.Sprintf( - "Endpoint not found. Did you mean '%s'? See /docs for the API documentation.", + "Endpoint not found. Did you mean '%s' or '%s'? See /docs for the API documentation.", + "/v0.1"+path, "/v0"+path, ) } @@ -145,6 +146,7 @@ func NewHumaAPI(cfg *config.Config, registry service.RegistryService, mux *http. // Register routes for all API versions RegisterV0Routes(api, cfg, registry, metrics) + RegisterV0_1Routes(api, cfg, registry, metrics) // Add /metrics for Prometheus metrics using promhttp mux.Handle("/metrics", metrics.PrometheusHandler()) diff --git a/internal/api/router/v0.go b/internal/api/router/v0.go index d1735a4..45e0210 100644 --- a/internal/api/router/v0.go +++ b/internal/api/router/v0.go @@ -14,10 +14,21 @@ import ( func RegisterV0Routes( api huma.API, cfg *config.Config, registry service.RegistryService, metrics *telemetry.Metrics, ) { - v0.RegisterHealthEndpoint(api, cfg, metrics) - v0.RegisterPingEndpoint(api) - v0.RegisterServersEndpoints(api, registry) - v0.RegisterEditEndpoints(api, registry, cfg) - v0auth.RegisterAuthEndpoints(api, cfg) - v0.RegisterPublishEndpoint(api, registry, cfg) + v0.RegisterHealthEndpoint(api, "/v0", cfg, metrics) + v0.RegisterPingEndpoint(api, "/v0") + v0.RegisterServersEndpoints(api, "/v0", registry) + v0.RegisterEditEndpoints(api, "/v0", registry, cfg) + v0auth.RegisterAuthEndpoints(api, "/v0", cfg) + v0.RegisterPublishEndpoint(api, "/v0", registry, cfg) +} + +func RegisterV0_1Routes( + api huma.API, cfg *config.Config, registry service.RegistryService, metrics *telemetry.Metrics, +) { + v0.RegisterHealthEndpoint(api, "/v0.1", cfg, metrics) + v0.RegisterPingEndpoint(api, "/v0.1") + v0.RegisterServersEndpoints(api, "/v0.1", registry) + v0.RegisterEditEndpoints(api, "/v0.1", registry, cfg) + v0auth.RegisterAuthEndpoints(api, "/v0.1", cfg) + v0.RegisterPublishEndpoint(api, "/v0.1", registry, cfg) }