From 4f453ff2bdbb29276e18ad94f83cda5b1a81869e Mon Sep 17 00:00:00 2001 From: adam jones Date: Tue, 7 Oct 2025 11:56:45 +0100 Subject: [PATCH] Drop GET /v0/servers/{serverName} endpoint (#618) Remove redundant endpoint as suggested in #617. Clients should now use `GET /v0/servers/{serverName}/versions/latest` instead (using `latest` as a special version value in the existing endpoint). This simplifies the API surface and reduces implementation burden for subregistries, while maintaining a clear migration path for consumers. ## Changes - Removed `GET /v0/servers/{serverName}` endpoint - Updated `GET /v0/servers/{serverName}/versions/{version}` to accept `latest` as special version - Updated all tests to use new endpoint pattern - Updated documentation and OpenAPI spec - Added breaking change notice to CHANGELOG Fixes #617 Co-authored-by: Claude --- docs/guides/consuming/use-rest-api.md | 5 ++- docs/reference/api/CHANGELOG.md | 11 ++++++ docs/reference/api/generic-registry-api.md | 5 ++- docs/reference/api/openapi.yaml | 31 +-------------- internal/api/handlers/v0/servers.go | 45 +++++----------------- internal/api/handlers/v0/servers_test.go | 10 ++--- internal/api/handlers/v0/telemetry_test.go | 6 +-- tests/integration/main.go | 2 +- 8 files changed, 37 insertions(+), 78 deletions(-) diff --git a/docs/guides/consuming/use-rest-api.md b/docs/guides/consuming/use-rest-api.md index 928b863..02e4fff 100644 --- a/docs/guides/consuming/use-rest-api.md +++ b/docs/guides/consuming/use-rest-api.md @@ -9,9 +9,10 @@ Integration patterns and best practices for building applications that consume M **Authentication**: Not required for read-only access - **`GET /v0/servers`** - List all servers with pagination -- **`GET /v0/servers/{serverName}`** - Get latest version of server by server name (URL-encoded) -- **`GET /v0/servers/{serverName}/versions/{version}`** - Get specific version of server - **`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. + +Server names and version strings should be URL-encoded in paths. See the [interactive API documentation](https://registry.modelcontextprotocol.io/docs) for complete request/response schemas. diff --git a/docs/reference/api/CHANGELOG.md b/docs/reference/api/CHANGELOG.md index 96fbb17..03464ca 100644 --- a/docs/reference/api/CHANGELOG.md +++ b/docs/reference/api/CHANGELOG.md @@ -2,6 +2,17 @@ Changes to the REST API endpoints and responses. +## Unreleased + +### ⚠️ BREAKING CHANGES + +#### Endpoint Simplification + +Removed redundant endpoint to simplify API surface and reduce implementation burden for subregistries. + +**Removed endpoints:** +- `GET /v0/servers/{serverName}` - Use `GET /v0/servers/{serverName}/versions/latest` instead + ## 2025-09-29 ### ⚠️ BREAKING CHANGES diff --git a/docs/reference/api/generic-registry-api.md b/docs/reference/api/generic-registry-api.md index 8190017..86cffa6 100644 --- a/docs/reference/api/generic-registry-api.md +++ b/docs/reference/api/generic-registry-api.md @@ -15,11 +15,12 @@ The official registry has some more endpoints and restrictions on top of this. S ### Core Endpoints - **`GET /v0/servers`** - List all servers with pagination -- **`GET /v0/servers/{serverName}`** - Get latest version of server by server name (URL-encoded) -- **`GET /v0/servers/{serverName}/versions/{version}`** - Get specific version of server (both parameters should be URL-encoded) - **`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. - **`POST /v0/publish`** - Publish new server (optional, registry-specific authentication) +Server names and version strings should be URL-encoded in paths. + ### Authentication - **Read operations**: No authentication required - **Write operations**: Registry-specific authentication (if supported) diff --git a/docs/reference/api/openapi.yaml b/docs/reference/api/openapi.yaml index 4c533c1..4e5a170 100644 --- a/docs/reference/api/openapi.yaml +++ b/docs/reference/api/openapi.yaml @@ -39,35 +39,6 @@ paths: application/json: schema: $ref: '#/components/schemas/ServerList' - /v0/servers/{serverName}: - get: - summary: Get MCP server details - description: Returns detailed information about the latest version of a specific MCP server. - parameters: - - name: serverName - in: path - required: true - description: URL-encoded server name (e.g., "com.example%2Fmy-server") - schema: - type: string - example: "com.example%2Fmy-server" - responses: - '200': - description: Detailed server information - content: - application/json: - schema: - $ref: '#/components/schemas/ServerResponse' - '404': - description: Server not found - content: - application/json: - schema: - type: object - properties: - error: - type: string - example: "Server not found" /v0/servers/{serverName}/versions: get: summary: List all versions of an MCP server @@ -100,7 +71,7 @@ paths: /v0/servers/{serverName}/versions/{version}: get: summary: Get specific MCP server version - description: Returns detailed information about a specific version of an MCP server. + description: Returns detailed information about a specific version of an MCP server. Use the special version `latest` to get the latest version. parameters: - name: serverName in: path diff --git a/internal/api/handlers/v0/servers.go b/internal/api/handlers/v0/servers.go index 9628ffe..ecc2a9b 100644 --- a/internal/api/handlers/v0/servers.go +++ b/internal/api/handlers/v0/servers.go @@ -41,8 +41,6 @@ type ServerVersionsInput struct { } // RegisterServersEndpoints registers all server-related endpoints -// -//nolint:cyclop // Multiple endpoint registrations are inherently complex func RegisterServersEndpoints(api huma.API, registry service.RegistryService) { // List servers endpoint huma.Register(api, huma.Operation{ @@ -106,42 +104,13 @@ func RegisterServersEndpoints(api huma.API, registry service.RegistryService) { }, nil }) - // Get server details endpoint (latest version) - huma.Register(api, huma.Operation{ - OperationID: "get-server", - Method: http.MethodGet, - Path: "/v0/servers/{serverName}", - Summary: "Get MCP server details", - Description: "Get detailed information about the latest version of a specific MCP server.", - Tags: []string{"servers"}, - }, func(ctx context.Context, input *ServerDetailInput) (*Response[apiv0.ServerResponse], error) { - // URL-decode the server name - serverName, err := url.PathUnescape(input.ServerName) - if err != nil { - return nil, huma.Error400BadRequest("Invalid server name encoding", err) - } - - // Get latest version by server name - serverResponse, err := registry.GetServerByName(ctx, serverName) - if err != nil { - if err.Error() == errRecordNotFound || errors.Is(err, database.ErrNotFound) { - return nil, huma.Error404NotFound("Server not found") - } - return nil, huma.Error500InternalServerError("Failed to get server details", err) - } - - return &Response[apiv0.ServerResponse]{ - Body: *serverResponse, - }, nil - }) - - // Get specific server version endpoint + // Get specific server version endpoint (supports "latest" as special version) huma.Register(api, huma.Operation{ OperationID: "get-server-version", Method: http.MethodGet, Path: "/v0/servers/{serverName}/versions/{version}", Summary: "Get specific MCP server version", - Description: "Get detailed information about a specific version of an MCP server.", + 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"}, }, func(ctx context.Context, input *ServerVersionDetailInput) (*Response[apiv0.ServerResponse], error) { // URL-decode the server name @@ -156,8 +125,14 @@ func RegisterServersEndpoints(api huma.API, registry service.RegistryService) { return nil, huma.Error400BadRequest("Invalid version encoding", err) } - // Get specific version by server name and version - serverResponse, err := registry.GetServerByNameAndVersion(ctx, serverName, version) + var serverResponse *apiv0.ServerResponse + // Handle "latest" as a special version + if version == "latest" { + serverResponse, err = registry.GetServerByName(ctx, serverName) + } else { + serverResponse, err = registry.GetServerByNameAndVersion(ctx, serverName, version) + } + if err != nil { if err.Error() == errRecordNotFound || errors.Is(err, database.ErrNotFound) { return nil, huma.Error404NotFound("Server not found") diff --git a/internal/api/handlers/v0/servers_test.go b/internal/api/handlers/v0/servers_test.go index d7e379f..8dc9f66 100644 --- a/internal/api/handlers/v0/servers_test.go +++ b/internal/api/handlers/v0/servers_test.go @@ -114,7 +114,7 @@ func TestListServersEndpoint(t *testing.T) { } } -func TestGetServerByNameEndpoint(t *testing.T) { +func TestGetLatestServerVersionEndpoint(t *testing.T) { ctx := context.Background() registryService := service.NewRegistryService(database.NewTestDB(t), config.NewConfig()) @@ -139,7 +139,7 @@ func TestGetServerByNameEndpoint(t *testing.T) { expectedError string }{ { - name: "get existing server", + name: "get existing server latest version", serverName: "com.example/detail-server", expectedStatus: http.StatusOK, }, @@ -155,7 +155,7 @@ func TestGetServerByNameEndpoint(t *testing.T) { t.Run(tt.name, func(t *testing.T) { // URL encode the server name encodedName := url.PathEscape(tt.serverName) - req := httptest.NewRequest(http.MethodGet, "/v0/servers/"+encodedName, nil) + req := httptest.NewRequest(http.MethodGet, "/v0/servers/"+encodedName+"/versions/latest", nil) w := httptest.NewRecorder() mux.ServeHTTP(w, req) @@ -434,9 +434,9 @@ func TestServersEndpointEdgeCases(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - // Test server detail endpoint + // Test latest version endpoint encodedName := url.PathEscape(tt.serverName) - req := httptest.NewRequest(http.MethodGet, "/v0/servers/"+encodedName, nil) + req := httptest.NewRequest(http.MethodGet, "/v0/servers/"+encodedName+"/versions/latest", nil) w := httptest.NewRecorder() mux.ServeHTTP(w, req) diff --git a/internal/api/handlers/v0/telemetry_test.go b/internal/api/handlers/v0/telemetry_test.go index 1e797d6..e0708b4 100644 --- a/internal/api/handlers/v0/telemetry_test.go +++ b/internal/api/handlers/v0/telemetry_test.go @@ -52,8 +52,8 @@ func TestPrometheusHandler(t *testing.T) { // Add /metrics for Prometheus metrics using promhttp mux.Handle("/metrics", metrics.PrometheusHandler()) - // Create request - using new name-based API - url := "/v0/servers/" + url.PathEscape(server.Server.Name) + // Create request - using latest version endpoint + url := "/v0/servers/" + url.PathEscape(server.Server.Name) + "/versions/latest" req := httptest.NewRequest(http.MethodGet, url, nil) w := httptest.NewRecorder() @@ -78,5 +78,5 @@ func TestPrometheusHandler(t *testing.T) { // Check if the response body contains expected metrics assert.Contains(t, body, "mcp_registry_http_request_duration_bucket") assert.Contains(t, body, "mcp_registry_http_requests_total") - assert.Contains(t, body, "path=\"/v0/servers/{serverName}\"") + assert.Contains(t, body, "path=\"/v0/servers/{serverName}/versions/{version}\"") } diff --git a/tests/integration/main.go b/tests/integration/main.go index d6e8a11..889780f 100644 --- a/tests/integration/main.go +++ b/tests/integration/main.go @@ -245,7 +245,7 @@ func verifyPublishedServer(serverName string, expected *apiv0.ServerJSON) error // URL encode the server name since it contains special characters like "/" encodedName := url.PathEscape(serverName) - req, err := http.NewRequestWithContext(ctx, http.MethodGet, registryURL+"/v0/servers/"+encodedName, nil) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, registryURL+"/v0/servers/"+encodedName+"/versions/latest", nil) if err != nil { return err }