Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 0 additions & 1 deletion registry/converters/registry_converters.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ func NewUpstreamRegistryFromToolhiveRegistry(toolhiveReg *registry.Registry) (*r
},
Data: registry.UpstreamData{
Servers: servers,
Groups: []registry.UpstreamGroup{},
},
}, nil
}
29 changes: 0 additions & 29 deletions registry/types/data/upstream-registry.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,35 +48,6 @@
"$ref": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json"
}
},
"groups": {
"type": "array",
"description": "Groups of related MCP servers",
"items": {
"type": "object",
"required": [
"name",
"description",
"servers"
],
"properties": {
"name": {
"type": "string",
"description": "Unique identifier for the group"
},
"description": {
"type": "string",
"description": "Description of the group's purpose"
},
"servers": {
"type": "array",
"description": "Array of servers in this group",
"items": {
"$ref": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json"
}
}
}
}
},
"skills": {
"type": "array",
"description": "Array of skills in the registry",
Expand Down
25 changes: 2 additions & 23 deletions registry/types/schema_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,41 +195,20 @@ func validateRegistryExtensions(registryData []byte) error {

var errs []string
if servers, ok := data["servers"].([]any); ok {
errs = append(errs, validateServerList(servers, "")...)
}
if groups, ok := data["groups"].([]any); ok {
errs = append(errs, validateGroupServers(groups)...)
errs = append(errs, validateServerList(servers)...)
}

return formatExtensionErrors(errs)
}

func validateGroupServers(groups []any) []string {
var errs []string
for _, group := range groups {
groupMap, ok := group.(map[string]any)
if !ok {
continue
}
groupName, _ := groupMap["name"].(string)
if groupServers, ok := groupMap["servers"].([]any); ok {
errs = append(errs, validateServerList(groupServers, groupName)...)
}
}
return errs
}

func validateServerList(servers []any, groupName string) []string {
func validateServerList(servers []any) []string {
var errs []string
for i, server := range servers {
serverMap, ok := server.(map[string]any)
if !ok {
continue
}
serverName := getServerName(serverMap, i)
if groupName != "" {
serverName = fmt.Sprintf("group[%s].%s", groupName, serverName)
}
if err := validateServerExtensions(serverMap, serverName); err != nil {
errs = append(errs, err.Error())
}
Expand Down
147 changes: 1 addition & 146 deletions registry/types/schema_validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,21 +28,6 @@ func TestValidateUpstreamRegistryBytes(t *testing.T) {
}{
{
name: "valid registry with all fields",
data: `{
"$schema": "https://raw.githubusercontent.com/stacklok/toolhive-core/main/registry/types/data/upstream-registry.schema.json",
"version": "1.0.0",
"meta": {
"last_updated": "2024-01-15T10:30:00Z"
},
"data": {
"servers": [],
"groups": []
}
}`,
wantErr: false,
},
{
name: "valid registry without groups (optional)",
data: `{
"$schema": "https://raw.githubusercontent.com/stacklok/toolhive-core/main/registry/types/data/upstream-registry.schema.json",
"version": "1.0.0",
Expand All @@ -55,27 +40,6 @@ func TestValidateUpstreamRegistryBytes(t *testing.T) {
}`,
wantErr: false,
},
{
name: "valid registry with group",
data: `{
"$schema": "https://raw.githubusercontent.com/stacklok/toolhive-core/main/registry/types/data/upstream-registry.schema.json",
"version": "1.0.0",
"meta": {
"last_updated": "2024-01-15T10:30:00Z"
},
"data": {
"servers": [],
"groups": [
{
"name": "test-group",
"description": "Test group",
"servers": []
}
]
}
}`,
wantErr: false,
},
{
name: "missing meta",
data: `{
Expand Down Expand Up @@ -150,25 +114,6 @@ func TestValidateUpstreamRegistryBytes(t *testing.T) {
wantErr: true,
errorContains: "date-time",
},
{
name: "missing required group fields",
data: `{
"version": "1.0.0",
"meta": {
"last_updated": "2024-01-15T10:30:00Z"
},
"data": {
"servers": [],
"groups": [
{
"name": "incomplete-group"
}
]
}
}`,
wantErr: true,
errorContains: errKeyDescription,
},
}

for _, tt := range tests {
Expand Down Expand Up @@ -207,8 +152,7 @@ func TestValidateUpstreamRegistry_RealWorld(t *testing.T) {
"version": "1.0.0",
"title": "Test Server"
}
],
"groups": []
]
}
}`

Expand Down Expand Up @@ -736,77 +680,6 @@ func TestValidateUpstreamRegistry_WithExtensions(t *testing.T) {
wantErr: true,
errorContains: errKeyTier,
},
{
name: "valid registry with extensions in groups",
data: `{
"version": "1.0.0",
"meta": {
"last_updated": "2024-01-15T10:30:00Z"
},
"data": {
"servers": [],
"groups": [
{
"name": "test-group",
"description": "A test group",
"servers": [
{
"name": "io.github.stacklok/grouped-server",
"description": "A grouped server",
"version": "1.0.0",
"_meta": {
"io.modelcontextprotocol.registry/publisher-provided": {
"io.github.stacklok": {
"ghcr.io/test/grouped:v1.0.0": {
"status": "active"
}
}
}
}
}
]
}
]
}
}`,
wantErr: false,
},
{
name: "invalid extensions in group server",
data: `{
"version": "1.0.0",
"meta": {
"last_updated": "2024-01-15T10:30:00Z"
},
"data": {
"servers": [],
"groups": [
{
"name": "test-group",
"description": "A test group",
"servers": [
{
"name": "io.github.stacklok/grouped-server",
"description": "A grouped server",
"version": "1.0.0",
"_meta": {
"io.modelcontextprotocol.registry/publisher-provided": {
"io.github.stacklok": {
"ghcr.io/test/grouped:v1.0.0": {
"status": "invalid-status"
}
}
}
}
}
]
}
]
}
}`,
wantErr: true,
errorContains: errKeyStatus,
},
}

for _, tt := range tests {
Expand Down Expand Up @@ -1332,24 +1205,6 @@ func TestUpstreamRegistrySchemaVersionSync(t *testing.T) {
schemaPath, schemaDate, expectedDate, expectedDate, expectedDate)
}

// Also check groups schema if present
groupServerItems, ok := walkJSONObjects(schema, "properties", "data", "properties", "groups", "items", "properties", "servers", "items")
if ok {
groupRefURL, ok := groupServerItems["$ref"].(string)
if ok {
groupMatches := re.FindStringSubmatch(groupRefURL)
if len(groupMatches) == 2 {
groupSchemaDate := groupMatches[1]
if groupSchemaDate != expectedDate {
t.Errorf("Groups schema version mismatch!\n"+
" Groups $ref date: %s\n"+
" Expected: %s\n\n"+
"To fix: Update data.properties.groups.items.properties.servers.items.$ref",
groupSchemaDate, expectedDate)
}
}
}
}
}

// findExternalRefs recursively walks a parsed JSON value and collects all
Expand Down
19 changes: 2 additions & 17 deletions registry/types/upstream_registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const UpstreamRegistrySchemaURL = "https://raw.githubusercontent.com/stacklok/to
"registry/types/data/upstream-registry.schema.json"

// UpstreamRegistry is the unified registry format that stores servers in upstream
// ServerJSON format with proper meta/data separation and groups support.
// ServerJSON format with proper meta/data separation.
type UpstreamRegistry struct {
// Schema is the JSON schema URL for validation
Schema string `json:"$schema" yaml:"$schema"`
Expand All @@ -33,26 +33,11 @@ type UpstreamMeta struct {
LastUpdated string `json:"last_updated" yaml:"last_updated"`
}

// UpstreamData contains the actual registry content (servers, groups, and skills)
// UpstreamData contains the actual registry content (servers and skills)
type UpstreamData struct {
// Servers contains the server definitions in upstream MCP format
Servers []upstreamv0.ServerJSON `json:"servers" yaml:"servers"`

// Groups contains grouped collections of servers (optional, for future use)
Groups []UpstreamGroup `json:"groups,omitempty" yaml:"groups,omitempty"`

// Skills contains the skill definitions
Skills []Skill `json:"skills,omitempty" yaml:"skills,omitempty"`
}

// UpstreamGroup represents a named collection of related MCP servers
type UpstreamGroup struct {
// Name is the unique identifier for the group
Name string `json:"name" yaml:"name"`

// Description explains the purpose of this group
Description string `json:"description" yaml:"description"`

// Servers contains the server definitions in this group
Servers []upstreamv0.ServerJSON `json:"servers" yaml:"servers"`
}
Loading
Loading