Skip to content

Commit d2bfd0b

Browse files
tadasantclaude
andcommitted
Implement version-specific endpoints for /servers/{id}
- Update /servers/{id} endpoint to return latest version by default - Add /servers/{id}?version={version} endpoint for specific versions - Add /servers/{id}/versions endpoint to list all versions - Enhance database layer with ServerID filter support - Update service layer with new methods: GetByIDAndVersion, GetVersionsByID - Update API documentation and OpenAPI specification - Refactor PostgreSQL filter logic to reduce complexity - All tests pass and endpoints tested locally 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent 968b203 commit d2bfd0b

File tree

28 files changed

+346
-151
lines changed

28 files changed

+346
-151
lines changed

cmd/publisher/commands/init.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,7 @@ func createServerJSON(
309309
URL: repoURL,
310310
Source: repoSource,
311311
},
312-
Version: version,
312+
Version: version,
313313
Packages: []model.Package{pkg},
314314
}
315315
}

cmd/publisher/commands/logout.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ func LogoutCommand() error {
1313
}
1414

1515
tokenPath := filepath.Join(homeDir, TokenFileName)
16-
16+
1717
// Check if token file exists
1818
if _, err := os.Stat(tokenPath); os.IsNotExist(err) {
1919
_, _ = fmt.Fprintln(os.Stdout, "Not logged in")
@@ -30,7 +30,7 @@ func LogoutCommand() error {
3030
".mcpregistry_github_token",
3131
".mcpregistry_registry_token",
3232
}
33-
33+
3434
for _, file := range legacyFiles {
3535
path := filepath.Join(homeDir, file)
3636
if _, err := os.Stat(path); err == nil {
@@ -40,4 +40,4 @@ func LogoutCommand() error {
4040

4141
_, _ = fmt.Fprintln(os.Stdout, "✓ Successfully logged out")
4242
return nil
43-
}
43+
}

cmd/publisher/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,4 +67,4 @@ func printUsage() {
6767
_, _ = fmt.Fprintln(os.Stdout, " publish Publish server.json to the registry")
6868
_, _ = fmt.Fprintln(os.Stdout)
6969
_, _ = fmt.Fprintln(os.Stdout, "Use 'mcp-publisher <command> --help' for more information about a command.")
70-
}
70+
}

deploy/pkg/k8s/ingress.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ func SetupIngressController(ctx *pulumi.Context, cluster *providers.ProviderInfo
5151
Values: pulumi.Map{
5252
"controller": pulumi.Map{
5353
"service": pulumi.Map{
54-
"type": serviceType,
54+
"type": serviceType,
5555
"annotations": pulumi.Map{},
5656
},
5757
"config": pulumi.Map{

deploy/pkg/providers/gcp/provider.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,19 +22,19 @@ type Provider struct{}
2222
// createGCPProvider creates a GCP provider with explicit credentials if configured
2323
func createGCPProvider(ctx *pulumi.Context, name string) (*gcp.Provider, error) {
2424
gcpConf := config.New(ctx, "gcp")
25-
25+
2626
// Get project ID from config
2727
projectID := gcpConf.Get("project")
2828
if projectID == "" {
2929
return nil, fmt.Errorf("GCP project ID not configured. Set gcp:project")
3030
}
31-
31+
3232
// Get region from config or use default
3333
region := gcpConf.Get("region")
3434
if region == "" {
3535
region = "us-central1"
3636
}
37-
37+
3838
// Get credentials from config (base64 encoded service account JSON)
3939
credentials := gcpConf.Get("credentials")
4040
if credentials != "" {
@@ -45,7 +45,7 @@ func createGCPProvider(ctx *pulumi.Context, name string) (*gcp.Provider, error)
4545
}
4646
credentials = string(decodedCreds)
4747
}
48-
48+
4949
// Create a GCP provider with explicit credentials if provided
5050
if credentials != "" {
5151
return gcp.NewProvider(ctx, name, &gcp.ProviderArgs{
@@ -54,7 +54,7 @@ func createGCPProvider(ctx *pulumi.Context, name string) (*gcp.Provider, error)
5454
Credentials: pulumi.String(credentials),
5555
})
5656
}
57-
57+
5858
return nil, nil
5959
}
6060

docs/reference/api/openapi.yaml

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ paths:
3939
/v0/servers/{id}:
4040
get:
4141
summary: Get MCP server details
42-
description: Returns detailed information about a specific MCP server
42+
description: Returns detailed information about a specific MCP server. By default, returns the latest version. Use the 'version' query parameter to retrieve a specific version.
4343
parameters:
4444
- name: id
4545
in: path
@@ -50,9 +50,10 @@ paths:
5050
format: uuid
5151
- name: version
5252
in: query
53-
description: Desired MCP server version
53+
description: Specific version to retrieve (optional). If not provided, returns the latest version.
5454
schema:
5555
type: string
56+
example: "1.0.2"
5657
responses:
5758
'200':
5859
description: Detailed server information
@@ -70,6 +71,40 @@ paths:
7071
error:
7172
type: string
7273
example: "Server not found"
74+
/v0/servers/{id}/versions:
75+
get:
76+
summary: Get all versions of an MCP server
77+
description: Returns a list of all versions for a specific MCP server
78+
parameters:
79+
- name: id
80+
in: path
81+
required: true
82+
description: Unique ID of the server
83+
schema:
84+
type: string
85+
format: uuid
86+
responses:
87+
'200':
88+
description: List of all server versions
89+
content:
90+
application/json:
91+
schema:
92+
type: object
93+
properties:
94+
versions:
95+
type: array
96+
items:
97+
$ref: '#/components/schemas/ServerDetail'
98+
'404':
99+
description: Server not found
100+
content:
101+
application/json:
102+
schema:
103+
type: object
104+
properties:
105+
error:
106+
type: string
107+
example: "Server not found"
73108
/v0/publish:
74109
post:
75110
summary: Publish MCP server (Optional)

internal/api/handlers/v0/publish_integration_test.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -160,9 +160,9 @@ func TestPublishIntegration(t *testing.T) {
160160

161161
t.Run("publish fails with invalid token", func(t *testing.T) {
162162
publishReq := apiv0.ServerJSON{
163-
Name: "io.github.domdomegg/test-server",
164-
Description: "Test server",
165-
Version: "1.0.0",
163+
Name: "io.github.domdomegg/test-server",
164+
Description: "Test server",
165+
Version: "1.0.0",
166166
}
167167

168168
body, err := json.Marshal(publishReq)
@@ -183,7 +183,7 @@ func TestPublishIntegration(t *testing.T) {
183183
publishReq := apiv0.ServerJSON{
184184
Name: "io.github.other/test-server",
185185
Description: "A test server",
186-
Version: "1.0.0",
186+
Version: "1.0.0",
187187
Repository: model.Repository{
188188
URL: "https://github.com/example/test-server",
189189
Source: "github",
@@ -219,8 +219,8 @@ func TestPublishIntegration(t *testing.T) {
219219
publishReq := apiv0.ServerJSON{
220220
Name: "io.github.domdomegg/airtable-mcp-server",
221221
Description: "A test server with MCPB package",
222-
Version: "1.7.2",
223-
Status: model.StatusActive,
222+
Version: "1.7.2",
223+
Status: model.StatusActive,
224224
Packages: []model.Package{
225225
{
226226
RegistryType: model.RegistryTypeMCPB,

internal/api/handlers/v0/publish_registry_validation_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ func TestPublishRegistryValidation(t *testing.T) {
4747
publishReq := apiv0.ServerJSON{
4848
Name: "com.example/test-server-with-npm",
4949
Description: "A test server with invalid npm package reference",
50-
Version: "1.0.0",
50+
Version: "1.0.0",
5151
Packages: []model.Package{
5252
{
5353
RegistryType: model.RegistryTypeNPM,
@@ -88,7 +88,7 @@ func TestPublishRegistryValidation(t *testing.T) {
8888
publishReq := apiv0.ServerJSON{
8989
Name: "com.example/test-server-mcpb-validation",
9090
Description: "A test server with MCPB package and registry validation enabled",
91-
Version: "0.0.36",
91+
Version: "0.0.36",
9292
Packages: []model.Package{
9393
{
9494
RegistryType: model.RegistryTypeMCPB,
@@ -138,7 +138,7 @@ func TestPublishRegistryValidation(t *testing.T) {
138138
publishReq := apiv0.ServerJSON{
139139
Name: "com.example/test-server-multiple-packages",
140140
Description: "A test server with multiple packages where second fails",
141-
Version: "1.0.0",
141+
Version: "1.0.0",
142142
Packages: []model.Package{
143143
{
144144
RegistryType: model.RegistryTypeMCPB,
@@ -189,7 +189,7 @@ func TestPublishRegistryValidation(t *testing.T) {
189189
publishReq := apiv0.ServerJSON{
190190
Name: "com.example/test-server-first-package-fails",
191191
Description: "A test server where first package fails",
192-
Version: "1.0.0",
192+
Version: "1.0.0",
193193
Packages: []model.Package{
194194
{
195195
RegistryType: model.RegistryTypeNPM,

internal/api/handlers/v0/publish_test.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -113,9 +113,9 @@ func TestPublishEndpoint(t *testing.T) {
113113
{
114114
name: "invalid authorization header format",
115115
requestBody: apiv0.ServerJSON{
116-
Name: "io.github.domdomegg/test-server",
117-
Description: "Test server",
118-
Version: "1.0.0",
116+
Name: "io.github.domdomegg/test-server",
117+
Description: "Test server",
118+
Version: "1.0.0",
119119
},
120120
authHeader: "InvalidFormat",
121121
setupRegistryService: func(_ service.RegistryService) {
@@ -129,7 +129,7 @@ func TestPublishEndpoint(t *testing.T) {
129129
requestBody: apiv0.ServerJSON{
130130
Name: "test-server",
131131
Description: "A test server",
132-
Version: "1.0.0",
132+
Version: "1.0.0",
133133
},
134134
authHeader: "Bearer invalidToken",
135135
setupRegistryService: func(_ service.RegistryService) {
@@ -143,7 +143,7 @@ func TestPublishEndpoint(t *testing.T) {
143143
requestBody: apiv0.ServerJSON{
144144
Name: "io.github.other/test-server",
145145
Description: "A test server",
146-
Version: "1.0.0",
146+
Version: "1.0.0",
147147
Repository: model.Repository{
148148
URL: "https://github.com/example/test-server",
149149
Source: "github",
@@ -167,7 +167,7 @@ func TestPublishEndpoint(t *testing.T) {
167167
requestBody: apiv0.ServerJSON{
168168
Name: "example/test-server",
169169
Description: "A test server",
170-
Version: "1.0.0",
170+
Version: "1.0.0",
171171
Repository: model.Repository{
172172
URL: "https://github.com/example/test-server",
173173
Source: "github",
@@ -185,7 +185,7 @@ func TestPublishEndpoint(t *testing.T) {
185185
existingServer := apiv0.ServerJSON{
186186
Name: "example/test-server",
187187
Description: "Existing test server",
188-
Version: "1.0.0",
188+
Version: "1.0.0",
189189
Repository: model.Repository{
190190
URL: "https://github.com/example/test-server-existing",
191191
Source: "github",
@@ -202,7 +202,7 @@ func TestPublishEndpoint(t *testing.T) {
202202
requestBody: apiv0.ServerJSON{
203203
Name: "com.example/test-server-mcpb",
204204
Description: "A test server with MCPB package",
205-
Version: "1.0.0",
205+
Version: "1.0.0",
206206
Packages: []model.Package{
207207
{
208208
RegistryType: model.RegistryTypeMCPB,

internal/api/handlers/v0/servers.go

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,20 @@ type ListServersBody struct {
3636

3737
// ServerDetailInput represents the input for getting server details
3838
type ServerDetailInput struct {
39+
ID string `path:"id" doc:"Server ID (UUID)" format:"uuid"`
40+
Version string `query:"version" doc:"Specific version to retrieve (optional)" required:"false" example:"1.0.0"`
41+
}
42+
43+
// ServerVersionsInput represents the input for getting server versions
44+
type ServerVersionsInput struct {
3945
ID string `path:"id" doc:"Server ID (UUID)" format:"uuid"`
4046
}
4147

48+
// ServerVersionsBody represents the response body for server versions
49+
type ServerVersionsBody struct {
50+
Versions []apiv0.ServerJSON `json:"versions" doc:"List of all versions for the server"`
51+
}
52+
4253
// RegisterServersEndpoints registers all server-related endpoints
4354
func RegisterServersEndpoints(api huma.API, registry service.RegistryService) {
4455
// List servers endpoint
@@ -118,11 +129,21 @@ func RegisterServersEndpoints(api huma.API, registry service.RegistryService) {
118129
Method: http.MethodGet,
119130
Path: "/v0/servers/{id}",
120131
Summary: "Get MCP server details",
121-
Description: "Get detailed information about a specific MCP server",
132+
Description: "Get detailed information about a specific MCP server. Returns the latest version by default, or a specific version if the 'version' query parameter is provided.",
122133
Tags: []string{"servers"},
123134
}, func(_ context.Context, input *ServerDetailInput) (*Response[apiv0.ServerJSON], error) {
124-
// Get the server details from the registry service
125-
serverDetail, err := registry.GetByID(input.ID)
135+
var serverDetail *apiv0.ServerJSON
136+
var err error
137+
138+
// Check if a specific version is requested
139+
if input.Version != "" {
140+
// Get specific version
141+
serverDetail, err = registry.GetByIDAndVersion(input.ID, input.Version)
142+
} else {
143+
// Get latest version
144+
serverDetail, err = registry.GetByID(input.ID)
145+
}
146+
126147
if err != nil {
127148
if err.Error() == "record not found" {
128149
return nil, huma.Error404NotFound("Server not found")
@@ -134,4 +155,29 @@ func RegisterServersEndpoints(api huma.API, registry service.RegistryService) {
134155
Body: *serverDetail,
135156
}, nil
136157
})
158+
159+
// Get server versions endpoint
160+
huma.Register(api, huma.Operation{
161+
OperationID: "get-server-versions",
162+
Method: http.MethodGet,
163+
Path: "/v0/servers/{id}/versions",
164+
Summary: "Get all versions of an MCP server",
165+
Description: "Get a list of all versions for a specific MCP server",
166+
Tags: []string{"servers"},
167+
}, func(_ context.Context, input *ServerVersionsInput) (*Response[ServerVersionsBody], error) {
168+
// Get all versions from the registry service
169+
versions, err := registry.GetVersionsByID(input.ID)
170+
if err != nil {
171+
if err.Error() == "record not found" {
172+
return nil, huma.Error404NotFound("Server not found")
173+
}
174+
return nil, huma.Error500InternalServerError("Failed to get server versions", err)
175+
}
176+
177+
return &Response[ServerVersionsBody]{
178+
Body: ServerVersionsBody{
179+
Versions: versions,
180+
},
181+
}, nil
182+
})
137183
}

0 commit comments

Comments
 (0)