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 <noreply@anthropic.com>
This commit is contained in:
adam jones 2025-10-07 11:56:45 +01:00 • committed by GitHub
commit 4f453ff2bd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 37 additions and 78 deletions

View file

@ -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.

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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")

View file

@ -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)

View file

@ -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}\"")
}

View file

@ -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
}