diff --git a/internal/database/database.go b/internal/database/database.go index b4a797fa..7d81abde 100644 --- a/internal/database/database.go +++ b/internal/database/database.go @@ -28,6 +28,7 @@ type ServerFilter struct { SubstringName *string // for substring search on name Version *string // for exact version matching IsLatest *bool // for filtering latest versions only + Status *string // for exact status filtering (e.g. "active") IncludeDeleted *bool // for including deleted packages in results (default: exclude) } diff --git a/internal/database/postgres.go b/internal/database/postgres.go index b0b2d983..49c1c741 100644 --- a/internal/database/postgres.go +++ b/internal/database/postgres.go @@ -137,7 +137,11 @@ func buildFilterConditions(filter *ServerFilter, argIndex int) ([]string, []any, args = append(args, *filter.IsLatest) argIndex++ } - if filter.IncludeDeleted == nil || !*filter.IncludeDeleted { + if filter.Status != nil { + conditions = append(conditions, fmt.Sprintf("status = $%d", argIndex)) + args = append(args, *filter.Status) + argIndex++ + } else if filter.IncludeDeleted == nil || !*filter.IncludeDeleted { conditions = append(conditions, "status != 'deleted'") } diff --git a/internal/service/registry_service.go b/internal/service/registry_service.go index 16a91000..96934a8d 100644 --- a/internal/service/registry_service.go +++ b/internal/service/registry_service.go @@ -300,10 +300,12 @@ func pickLatestVersion(versions []*apiv0.ServerResponse, allowDeleted bool) *api // validateNoDuplicateRemoteURLs checks that no other server is using the same remote URLs func (s *registryServiceImpl) validateNoDuplicateRemoteURLs(ctx context.Context, tx pgx.Tx, serverDetail apiv0.ServerJSON) error { - // Check each remote URL in the new server for conflicts + // Check each remote URL in the new server for conflicts. + // Only check active servers — deleted and deprecated servers should not block URL reuse. for _, remote := range serverDetail.Remotes { - // Use filter to find servers with this remote URL - filter := &database.ServerFilter{RemoteURL: &remote.URL} + // Use filter to find active servers with this remote URL + activeStatus := string(model.StatusActive) + filter := &database.ServerFilter{RemoteURL: &remote.URL, Status: &activeStatus} conflictingServers, _, err := s.db.ListServers(ctx, tx, filter, "", 1000) if err != nil { diff --git a/internal/service/registry_service_test.go b/internal/service/registry_service_test.go index 05ec8477..3b1e7ae0 100644 --- a/internal/service/registry_service_test.go +++ b/internal/service/registry_service_test.go @@ -52,6 +52,22 @@ func TestValidateNoDuplicateRemoteURLs(t *testing.T) { require.NoError(t, err, "failed to create server: %v", err) } + // Create a deprecated server with a remote URL to test that deprecated servers + // do not block URL reuse (issue #1193). + deprecatedServer := &apiv0.ServerJSON{ + Schema: model.CurrentSchemaURL, + Name: "com.example/deprecated-server", + Description: "A deprecated server", + Version: "1.0.0", + Remotes: []model.Transport{ + {Type: "streamable-http", URL: "https://api.deprecated.example.com/mcp"}, + }, + } + _, err := service.CreateServer(ctx, deprecatedServer) + require.NoError(t, err, "failed to create deprecated server") + _, err = service.UpdateAllVersionsStatus(ctx, "com.example/deprecated-server", &StatusChangeRequest{NewStatus: model.StatusDeprecated}) + require.NoError(t, err, "failed to deprecate server") + tests := []struct { name string serverDetail apiv0.ServerJSON @@ -110,6 +126,19 @@ func TestValidateNoDuplicateRemoteURLs(t *testing.T) { }, expectError: false, }, + { + name: "duplicate URL from deprecated server - should pass (issue #1193)", + serverDetail: apiv0.ServerJSON{ + Schema: model.CurrentSchemaURL, + Name: "com.example/new-server-reuse-deprecated-url", + Description: "A new server reusing a deprecated server's URL", + Version: "1.0.0", + Remotes: []model.Transport{ + {Type: "streamable-http", URL: "https://api.deprecated.example.com/mcp"}, // Same URL as deprecated server + }, + }, + expectError: false, + }, } for _, tt := range tests {