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:
parent
6d2001b831
commit
4f453ff2bd
8 changed files with 37 additions and 78 deletions
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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}\"")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue