refactor: consolidate server list response types (#387)

## Summary
- Consolidate ServerListResponse and Metadata types into pkg/api/v0
- Remove duplication between handler and API types
- Change metadata from optional pointer to required value
- Remove unused Total field from Metadata
This commit is contained in:
adam jones 2025-09-09 04:47:27 -07:00 • committed by GitHub
commit 0eada9b3b4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 19 additions and 47 deletions

View file

@ -12,13 +12,6 @@ import (
apiv0 "github.com/modelcontextprotocol/registry/pkg/api/v0"
)
// Metadata contains pagination metadata
type Metadata struct {
NextCursor string `json:"next_cursor,omitempty"`
Count int `json:"count,omitempty"`
Total int `json:"total,omitempty"`
}
// ListServersInput represents the input for listing servers
type ListServersInput struct {
Cursor string `query:"cursor" doc:"Pagination cursor (UUID)" format:"uuid" required:"false" example:"550e8400-e29b-41d4-a716-446655440000"`
@ -28,12 +21,6 @@ type ListServersInput struct {
Version string `query:"version" doc:"Filter by version ('latest' for latest version, or an exact version like '1.2.3')" required:"false" example:"latest"`
}
// ListServersBody represents the paginated server list response body
type ListServersBody struct {
Servers []apiv0.ServerJSON `json:"servers" doc:"List of MCP servers with extensions"`
Metadata *Metadata `json:"metadata,omitempty" doc:"Pagination metadata"`
}
// ServerDetailInput represents the input for getting server details
type ServerDetailInput struct {
ID string `path:"id" doc:"Server ID (UUID)" format:"uuid"`
@ -49,7 +36,7 @@ func RegisterServersEndpoints(api huma.API, registry service.RegistryService) {
Summary: "List MCP servers",
Description: "Get a paginated list of MCP servers from the registry",
Tags: []string{"servers"},
}, func(_ context.Context, input *ListServersInput) (*Response[ListServersBody], error) {
}, func(_ context.Context, input *ListServersInput) (*Response[apiv0.ServerListResponse], error) {
// Validate cursor if provided
if input.Cursor != "" {
_, err := uuid.Parse(input.Cursor)
@ -94,21 +81,14 @@ func RegisterServersEndpoints(api huma.API, registry service.RegistryService) {
return nil, huma.Error500InternalServerError("Failed to get registry list", err)
}
// Build response body
body := ListServersBody{
Servers: servers,
}
// Add metadata if there's a next cursor
if nextCursor != "" {
body.Metadata = &Metadata{
NextCursor: nextCursor,
Count: len(servers),
}
}
return &Response[ListServersBody]{
Body: body,
return &Response[apiv0.ServerListResponse]{
Body: apiv0.ServerListResponse{
Servers: servers,
Metadata: apiv0.Metadata{
NextCursor: nextCursor,
Count: len(servers),
},
},
}, nil
})

View file

@ -26,7 +26,7 @@ func TestServersListEndpoint(t *testing.T) {
setupRegistryService func(service.RegistryService)
expectedStatus int
expectedServers []apiv0.ServerJSON
expectedMeta *v0.Metadata
expectedMeta *apiv0.Metadata
expectedError string
}{
{
@ -268,10 +268,7 @@ func TestServersListEndpoint(t *testing.T) {
assert.Equal(t, "application/json", w.Header().Get("Content-Type"))
// Parse response body
var resp struct {
Servers []apiv0.ServerJSON `json:"servers"`
Metadata *v0.Metadata `json:"metadata,omitempty"`
}
var resp apiv0.ServerListResponse
err := json.NewDecoder(w.Body).Decode(&resp)
assert.NoError(t, err)
@ -297,7 +294,7 @@ func TestServersListEndpoint(t *testing.T) {
default:
assert.NotEmpty(t, resp.Servers, "Expected at least one server")
}
// General structure validation
for _, server := range resp.Servers {
assert.NotEmpty(t, server.Name)
@ -311,11 +308,9 @@ func TestServersListEndpoint(t *testing.T) {
// Check metadata if expected
if tc.expectedMeta != nil {
assert.NotNil(t, resp.Metadata, "Expected metadata to be present")
if resp.Metadata != nil {
assert.Equal(t, tc.expectedMeta.Count, resp.Metadata.Count)
if tc.expectedMeta.NextCursor != "" {
assert.NotEmpty(t, resp.Metadata.NextCursor)
}
assert.Equal(t, tc.expectedMeta.Count, resp.Metadata.Count)
if tc.expectedMeta.NextCursor != "" {
assert.NotEmpty(t, resp.Metadata.NextCursor)
}
}
} else if tc.expectedError != "" {
@ -336,7 +331,7 @@ func TestServersDetailEndpoint(t *testing.T) {
testServer, err := registryService.Publish(apiv0.ServerJSON{
Name: "com.example/test-server",
Description: "A test server",
Version: "1.0.0",
Version: "1.0.0",
})
assert.NoError(t, err)
@ -466,9 +461,7 @@ func TestServersEndpointsIntegration(t *testing.T) {
assert.Equal(t, "application/json", resp.Header.Get("Content-Type"))
// Parse response body
var listResp struct {
Servers []apiv0.ServerJSON `json:"servers"`
}
var listResp apiv0.ServerListResponse
err = json.NewDecoder(resp.Body).Decode(&listResp)
assert.NoError(t, err)

View file

@ -17,7 +17,7 @@ type RegistryExtensions struct {
// ServerListResponse represents the paginated server list response
type ServerListResponse struct {
Servers []ServerJSON `json:"servers"`
Metadata *Metadata `json:"metadata,omitempty"`
Metadata Metadata `json:"metadata"`
}
// ServerMeta represents the structured metadata with known extension fields
@ -42,6 +42,5 @@ type ServerJSON struct {
// Metadata represents pagination metadata
type Metadata struct {
NextCursor string `json:"next_cursor,omitempty"`
Count int `json:"count,omitempty"`
Total int `json:"total,omitempty"`
Count int `json:"count"`
}