Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
7755fa1
Initial PoC of supporting the official MCP registry
rdimitrov Oct 30, 2025
201c3a9
Temporarily copy the converters from toolhive-registry
rdimitrov Oct 30, 2025
65bc3f7
Complete the initial implementation
rdimitrov Oct 31, 2025
0c331c5
Support setting the registry API via REST
rdimitrov Nov 4, 2025
3c4291b
Move the whole converters package from toolhive-registry
rdimitrov Nov 5, 2025
66cb469
Fix the imports in the tests
rdimitrov Nov 5, 2025
996c200
Config changes now take effect immediately without a restart
rdimitrov Nov 5, 2025
902a4e0
Fix a small issue with converting the names
rdimitrov Nov 5, 2025
2c3ed70
Normalise the full name instead of simplifying it
rdimitrov Nov 5, 2025
5308563
Fix imports after rebase - update to use pkg/registry/types
rdimitrov Nov 5, 2025
6e123ae
Fix imports after package reorganization rebase
rdimitrov Nov 6, 2025
fbe5b3e
Fix CodeQL warning and regenerate swagger docs
rdimitrov Nov 6, 2025
dbcc6c8
Fix another CodeQL error
rdimitrov Nov 6, 2025
1fe040e
Update MCP Registry API version from v0 to v0.1 in docs
rdimitrov Nov 6, 2025
0910e0c
Add User-Agent header to MCP Registry API client
rdimitrov Nov 6, 2025
df0410d
Extract environment variables from runtime arguments in MCP registry …
rdimitrov Nov 6, 2025
7affa01
Implement caching
rdimitrov Nov 7, 2025
1917edf
Update CLI documentation for registry commands
rdimitrov Nov 7, 2025
b536473
Regenerate the swagger files
rdimitrov Nov 10, 2025
f37a2fa
Consolidate set-registry-api into set-registry
rdimitrov Nov 10, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 15 additions & 15 deletions cmd/regup/app/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import (

"github.com/stacklok/toolhive/pkg/container/verifier"
"github.com/stacklok/toolhive/pkg/logger"
"github.com/stacklok/toolhive/pkg/registry"
regtypes "github.com/stacklok/toolhive/pkg/registry/types"
)

var (
Expand All @@ -29,7 +29,7 @@ var (

type serverWithName struct {
name string
server *registry.ImageMetadata
server *regtypes.ImageMetadata
}

// ProvenanceVerificationError represents an error during provenance verification
Expand Down Expand Up @@ -96,31 +96,31 @@ func updateCmdFunc(_ *cobra.Command, _ []string) error {
return saveResults(reg, updatedServers, failedServers)
}

func loadRegistry() (*registry.Registry, error) {
func loadRegistry() (*regtypes.Registry, error) {
registryPath := filepath.Join("pkg", "registry", "data", "registry.json")
// #nosec G304 -- This is a known file path
data, err := os.ReadFile(registryPath)
if err != nil {
return nil, fmt.Errorf("failed to read registry file: %w", err)
}

var reg registry.Registry
var reg regtypes.Registry
if err := json.Unmarshal(data, &reg); err != nil {
return nil, fmt.Errorf("failed to parse registry: %w", err)
}

return &reg, nil
}

func selectServersToUpdate(reg *registry.Registry) ([]serverWithName, error) {
func selectServersToUpdate(reg *regtypes.Registry) ([]serverWithName, error) {
if serverName != "" {
return selectSpecificServer(reg, serverName)
}

return selectOldestServers(reg)
}

func selectSpecificServer(reg *registry.Registry, name string) ([]serverWithName, error) {
func selectSpecificServer(reg *regtypes.Registry, name string) ([]serverWithName, error) {
server, exists := reg.Servers[name]
if !exists {
return nil, fmt.Errorf("server '%s' not found in registry", name)
Expand All @@ -129,7 +129,7 @@ func selectSpecificServer(reg *registry.Registry, name string) ([]serverWithName
return []serverWithName{{name: name, server: server}}, nil
}

func selectOldestServers(reg *registry.Registry) ([]serverWithName, error) {
func selectOldestServers(reg *regtypes.Registry) ([]serverWithName, error) {
servers := make([]serverWithName, 0, len(reg.Servers))
for name, server := range reg.Servers {
server.Name = name
Expand All @@ -151,7 +151,7 @@ func selectOldestServers(reg *registry.Registry) ([]serverWithName, error) {
return servers[:limit], nil
}

func isOlder(serverI, serverJ *registry.ImageMetadata) bool {
func isOlder(serverI, serverJ *regtypes.ImageMetadata) bool {
var lastUpdatedI, lastUpdatedJ string

if serverI.Metadata != nil {
Expand Down Expand Up @@ -180,7 +180,7 @@ func isOlder(serverI, serverJ *registry.ImageMetadata) bool {
return timeI.Before(timeJ)
}

func updateServers(servers []serverWithName, reg *registry.Registry) ([]string, []string) {
func updateServers(servers []serverWithName, reg *regtypes.Registry) ([]string, []string) {
updatedServers := make([]string, 0, len(servers))
failedServers := make([]string, 0)

Expand Down Expand Up @@ -209,7 +209,7 @@ func updateServers(servers []serverWithName, reg *registry.Registry) ([]string,
return updatedServers, failedServers
}

func saveResults(reg *registry.Registry, updatedServers []string, failedServers []string) error {
func saveResults(reg *regtypes.Registry, updatedServers []string, failedServers []string) error {
// If we're in dry run mode, don't save changes
if dryRun {
logger.Info("Dry run completed, no changes made")
Expand All @@ -235,7 +235,7 @@ func saveResults(reg *registry.Registry, updatedServers []string, failedServers
}

// updateServerInfo updates the GitHub stars and pulls for a server
func updateServerInfo(name string, server *registry.ImageMetadata) error {
func updateServerInfo(name string, server *regtypes.ImageMetadata) error {
// Verify provenance if requested
if verifyProvenance {
if err := verifyServerProvenance(name, server); err != nil {
Expand All @@ -254,7 +254,7 @@ func updateServerInfo(name string, server *registry.ImageMetadata) error {

// Initialize metadata if it's nil
if server.Metadata == nil {
server.Metadata = &registry.Metadata{}
server.Metadata = &regtypes.Metadata{}
}

// Extract owner and repo from repository URL
Expand Down Expand Up @@ -289,7 +289,7 @@ func updateServerInfo(name string, server *registry.ImageMetadata) error {
}

// verifyServerProvenance verifies the provenance information for a server
func verifyServerProvenance(name string, server *registry.ImageMetadata) error {
func verifyServerProvenance(name string, server *regtypes.ImageMetadata) error {
// Skip if no provenance information
if server.Provenance == nil {
logger.Warnf("Server %s has no provenance information, skipping verification", name)
Expand Down Expand Up @@ -325,7 +325,7 @@ func verifyServerProvenance(name string, server *registry.ImageMetadata) error {
}

// removeFailedServers removes servers that failed provenance verification from the registry
func removeFailedServers(reg *registry.Registry, failedServers []string) {
func removeFailedServers(reg *regtypes.Registry, failedServers []string) {
for _, serverName := range failedServers {
logger.Warnf("Removing server %s from registry due to provenance verification failure", serverName)
delete(reg.Servers, serverName)
Expand Down Expand Up @@ -420,7 +420,7 @@ func getGitHubRepoInfo(owner, repo, serverName string, currentPulls int) (stars
}

// saveRegistry saves the registry to the filesystem while preserving the order of entries
func saveRegistry(reg *registry.Registry, updatedServers []string, failedServers []string) error {
func saveRegistry(reg *regtypes.Registry, updatedServers []string, failedServers []string) error {
// Find the registry file path
registryPath := filepath.Join("pkg", "registry", "data", "registry.json")

Expand Down
24 changes: 12 additions & 12 deletions cmd/regup/app/update_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
"github.com/stretchr/testify/require"

"github.com/stacklok/toolhive/pkg/logger"
"github.com/stacklok/toolhive/pkg/registry"
regtypes "github.com/stacklok/toolhive/pkg/registry/types"
)

//nolint:paralleltest // This test manages temporary directories and cannot run in parallel
Expand Down Expand Up @@ -141,7 +141,7 @@ func TestServerSelection(t *testing.T) {
data, err := os.ReadFile(registryPath)
require.NoError(t, err)

var reg registry.Registry
var reg regtypes.Registry
err = json.Unmarshal(data, &reg)
require.NoError(t, err)

Expand Down Expand Up @@ -170,15 +170,15 @@ func setupTestRegistryWithMultipleServers(t *testing.T) (string, func()) {
require.NoError(t, err)

// Create test registry with multiple servers
testRegistry := &registry.Registry{
testRegistry := &regtypes.Registry{
LastUpdated: "2025-06-16T12:00:00Z",
Servers: map[string]*registry.ImageMetadata{
Servers: map[string]*regtypes.ImageMetadata{
"github": {
BaseServerMetadata: registry.BaseServerMetadata{
BaseServerMetadata: regtypes.BaseServerMetadata{
Name: "github",
Description: "GitHub MCP server",
RepositoryURL: "https://github.com/github/github-mcp-server",
Metadata: &registry.Metadata{
Metadata: &regtypes.Metadata{
Stars: 100,
Pulls: 5000,
LastUpdated: "2025-06-16T12:00:00Z", // Older
Expand All @@ -187,11 +187,11 @@ func setupTestRegistryWithMultipleServers(t *testing.T) (string, func()) {
Image: "ghcr.io/github/github-mcp-server:latest",
},
"gitlab": {
BaseServerMetadata: registry.BaseServerMetadata{
BaseServerMetadata: regtypes.BaseServerMetadata{
Name: "gitlab",
Description: "GitLab MCP server",
RepositoryURL: "https://github.com/example/gitlab-mcp-server",
Metadata: &registry.Metadata{
Metadata: &regtypes.Metadata{
Stars: 50,
Pulls: 2000,
LastUpdated: "2025-06-17T12:00:00Z", // Newer
Expand All @@ -200,11 +200,11 @@ func setupTestRegistryWithMultipleServers(t *testing.T) (string, func()) {
Image: "mcp/gitlab:latest",
},
"fetch": {
BaseServerMetadata: registry.BaseServerMetadata{
BaseServerMetadata: regtypes.BaseServerMetadata{
Name: "fetch",
Description: "Fetch MCP server",
RepositoryURL: "https://github.com/example/fetch-mcp-server",
Metadata: &registry.Metadata{
Metadata: &regtypes.Metadata{
Stars: 25,
Pulls: 1000,
LastUpdated: "2025-06-15T12:00:00Z", // Oldest
Expand Down Expand Up @@ -241,9 +241,9 @@ func setupEmptyTestRegistry(t *testing.T) (string, func()) {
require.NoError(t, err)

// Create empty test registry
testRegistry := &registry.Registry{
testRegistry := &regtypes.Registry{
LastUpdated: "2025-06-16T12:00:00Z",
Servers: map[string]*registry.ImageMetadata{},
Servers: map[string]*regtypes.ImageMetadata{},
}

// Write registry file
Expand Down
14 changes: 7 additions & 7 deletions cmd/thv-operator/pkg/filtering/filter_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ import (
"sigs.k8s.io/controller-runtime/pkg/log"

mcpv1alpha1 "github.com/stacklok/toolhive/cmd/thv-operator/api/v1alpha1"
"github.com/stacklok/toolhive/pkg/registry"
regtypes "github.com/stacklok/toolhive/pkg/registry/types"
)

// FilterService coordinates name and tag filtering to apply registry filters
type FilterService interface {
// ApplyFilters filters the registry based on MCPRegistry filter configuration
ApplyFilters(ctx context.Context, reg *registry.Registry, filter *mcpv1alpha1.RegistryFilter) (*registry.Registry, error)
ApplyFilters(ctx context.Context, reg *regtypes.Registry, filter *mcpv1alpha1.RegistryFilter) (*regtypes.Registry, error)
}

// DefaultFilterService implements filtering coordination using name and tag filters
Expand Down Expand Up @@ -49,8 +49,8 @@ func NewFilterService(nameFilter NameFilter, tagFilter TagFilter) *DefaultFilter
// 5. Return the filtered registry
func (s *DefaultFilterService) ApplyFilters(
ctx context.Context,
reg *registry.Registry,
filter *mcpv1alpha1.RegistryFilter) (*registry.Registry, error) {
reg *regtypes.Registry,
filter *mcpv1alpha1.RegistryFilter) (*regtypes.Registry, error) {
ctxLogger := log.FromContext(ctx)

// If no filter is specified, return original registry
Expand All @@ -64,11 +64,11 @@ func (s *DefaultFilterService) ApplyFilters(
"originalRemoteServerCount", len(reg.RemoteServers))

// Create a new filtered registry with same metadata
filteredRegistry := &registry.Registry{
filteredRegistry := &regtypes.Registry{
Version: reg.Version,
LastUpdated: reg.LastUpdated,
Servers: make(map[string]*registry.ImageMetadata),
RemoteServers: make(map[string]*registry.RemoteServerMetadata),
Servers: make(map[string]*regtypes.ImageMetadata),
RemoteServers: make(map[string]*regtypes.RemoteServerMetadata),
Groups: reg.Groups, // Groups are not filtered for now
}

Expand Down
Loading
Loading