Skip to content

Commit 8f09bca

Browse files
committed
Validate the generated registry against the schema
Signed-off-by: Radoslav Dimitrov <[email protected]>
1 parent 2f2a914 commit 8f09bca

File tree

5 files changed

+508
-1
lines changed

5 files changed

+508
-1
lines changed

go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,9 @@ require (
136136
github.com/transparency-dev/merkle v0.0.2 // indirect
137137
github.com/transparency-dev/tessera v0.2.1-0.20250610150926-8ee4e93b2823 // indirect
138138
github.com/vbatts/tar-split v0.12.1 // indirect
139+
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
140+
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
141+
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
139142
github.com/zalando/go-keyring v0.2.6 // indirect
140143
github.com/zeebo/errs v1.4.0 // indirect
141144
go.mongodb.org/mongo-driver v1.17.3 // indirect

go.sum

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1287,6 +1287,12 @@ github.com/transparency-dev/tessera v0.2.1-0.20250610150926-8ee4e93b2823 h1:s3p7
12871287
github.com/transparency-dev/tessera v0.2.1-0.20250610150926-8ee4e93b2823/go.mod h1:Jv2IDwG1q8QNXZTaI1X6QX8s96WlJn73ka2hT1n4N5c=
12881288
github.com/vbatts/tar-split v0.12.1 h1:CqKoORW7BUWBe7UL/iqTVvkTBOF8UvOMKOIZykxnnbo=
12891289
github.com/vbatts/tar-split v0.12.1/go.mod h1:eF6B6i6ftWQcDqEn3/iGFRFRo8cBIMSJVOpnNdfTMFA=
1290+
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
1291+
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
1292+
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
1293+
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
1294+
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
1295+
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
12901296
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
12911297
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
12921298
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=

pkg/registry/official.go

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/google/uuid"
1313
upstream "github.com/modelcontextprotocol/registry/pkg/api/v0"
1414
"github.com/modelcontextprotocol/registry/pkg/model"
15+
"github.com/xeipuuv/gojsonschema"
1516

1617
"github.com/stacklok/toolhive-registry/pkg/types"
1718
)
@@ -29,10 +30,16 @@ func NewOfficialRegistry(loader *Loader) *OfficialRegistry {
2930
}
3031

3132
// WriteJSON builds the official MCP registry and writes it to the specified path
33+
// The registry is validated against the schema before writing - generation fails if validation fails
3234
func (or *OfficialRegistry) WriteJSON(path string) error {
3335
// Build the registry structure
3436
registry := or.build()
3537

38+
// Validate the registry before writing
39+
if err := or.validateRegistry(registry); err != nil {
40+
return fmt.Errorf("registry validation failed: %w", err)
41+
}
42+
3643
// Create the directory if it doesn't exist
3744
dir := filepath.Dir(path)
3845
if err := os.MkdirAll(dir, 0750); err != nil {
@@ -53,6 +60,53 @@ func (or *OfficialRegistry) WriteJSON(path string) error {
5360
return nil
5461
}
5562

63+
// ValidateAgainstSchema validates the built registry against the schema
64+
func (or *OfficialRegistry) ValidateAgainstSchema() error {
65+
registry := or.build()
66+
return or.validateRegistry(registry)
67+
}
68+
69+
// validateRegistry validates a registry object against the schema
70+
func (*OfficialRegistry) validateRegistry(registry *ToolHiveRegistryType) error {
71+
// Marshal registry to JSON
72+
registryJSON, err := json.Marshal(registry)
73+
if err != nil {
74+
return fmt.Errorf("failed to marshal registry: %w", err)
75+
}
76+
77+
// Load schema from local file (fallback to remote if needed)
78+
schemaPath := "schemas/registry.schema.json"
79+
var schemaLoader gojsonschema.JSONLoader
80+
81+
// Try local schema first
82+
if _, err := os.Stat(schemaPath); err == nil {
83+
schemaLoader = gojsonschema.NewReferenceLoader("file://" + schemaPath)
84+
} else {
85+
// Fall back to remote schema
86+
schemaLoader = gojsonschema.NewReferenceLoader(
87+
"https://raw.githubusercontent.com/stacklok/toolhive-registry/main/schemas/registry.schema.json")
88+
}
89+
90+
// Create document loader from registry data
91+
documentLoader := gojsonschema.NewBytesLoader(registryJSON)
92+
93+
// Perform validation
94+
result, err := gojsonschema.Validate(schemaLoader, documentLoader)
95+
if err != nil {
96+
return fmt.Errorf("schema validation failed: %w", err)
97+
}
98+
99+
if !result.Valid() {
100+
var errorMessages []string
101+
for _, desc := range result.Errors() {
102+
errorMessages = append(errorMessages, desc.String())
103+
}
104+
return fmt.Errorf("validation errors: %v", errorMessages)
105+
}
106+
107+
return nil
108+
}
109+
56110
// build creates the ToolHiveRegistryType structure from loaded entries
57111
func (or *OfficialRegistry) build() *ToolHiveRegistryType {
58112
entries := or.loader.GetEntries()
@@ -73,7 +127,7 @@ func (or *OfficialRegistry) build() *ToolHiveRegistryType {
73127
}
74128

75129
registry := &ToolHiveRegistryType{
76-
Schema: "", // TODO: Add schema URL once applicable
130+
Schema: "https://raw.githubusercontent.com/stacklok/toolhive-registry/main/schemas/registry.schema.json",
77131
Version: "1.0.0",
78132
Meta: Meta{
79133
LastUpdated: time.Now().UTC().Format(time.RFC3339),

schemas/README.md

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# ToolHive Registry Schema
2+
3+
This directory contains the JSON Schema for the ToolHive registry format.
4+
5+
## Schema Overview
6+
7+
The schema defines the structure for ToolHive registry JSON files, leveraging the official MCP server schema for individual server entries.
8+
9+
**Schema Location:** `schemas/registry.schema.json`
10+
11+
**Schema URL:** `https://raw.githubusercontent.com/stacklok/toolhive-registry/main/schemas/registry.schema.json`
12+
13+
## Schema Design
14+
15+
### Upstream Integration
16+
17+
The schema reuses the official MCP server schema:
18+
`https://static.modelcontextprotocol.io/schemas/2025-07-09/server.schema.json`
19+
20+
**Benefits:**
21+
- **Standards compliance** with MCP specification
22+
- **Automatic compatibility** with MCP ecosystem tooling
23+
- **Reduced maintenance** - leverages upstream schema evolution
24+
- **Ecosystem integration** - works with existing MCP validators
25+
26+
### Registry Structure
27+
28+
```json
29+
{
30+
"$schema": "https://raw.githubusercontent.com/stacklok/toolhive-registry/main/schemas/registry.schema.json",
31+
"version": "1.0.0",
32+
"meta": {
33+
"last_updated": "2024-01-15T10:30:00Z"
34+
},
35+
"data": {
36+
"servers": [...], // Uses official MCP server schema
37+
"groups": [] // Placeholder for future grouping
38+
}
39+
}
40+
```
41+
42+
## Usage
43+
44+
### In Generated Registries
45+
46+
The schema URL is automatically included in all generated registry files:
47+
48+
```go
49+
// Generated registries automatically include the schema reference
50+
registry := NewOfficialRegistry(loader)
51+
registry.WriteJSON("output/registry.json")
52+
```
53+
54+
### With Validation Tools
55+
56+
JSON Schema validators can verify registry format:
57+
58+
```bash
59+
# Using ajv-cli
60+
npx ajv validate -s schemas/registry.schema.json -d output/registry.json
61+
62+
# Using jsonschema (Python)
63+
jsonschema -i output/registry.json schemas/registry.schema.json
64+
```
65+
66+
### IDE Support
67+
68+
IDEs with JSON Schema support will provide:
69+
- Auto-completion for registry structure
70+
- Validation errors and warnings
71+
- Documentation on hover
72+
73+
## Maintenance
74+
75+
### Schema Updates
76+
77+
For schema changes:
78+
79+
1. **Update schema file**: Edit `schemas/registry.schema.json`
80+
2. **Update ID if needed**: Update `$id` field if URL changes
81+
3. **Test changes**: Validate existing registries against updated schema
82+
4. **Commit schema**: Push changes to make schema available via GitHub
83+
84+
### Breaking Changes
85+
86+
For major structural changes:
87+
- Consider backwards compatibility
88+
- Update registry generation code accordingly
89+
- Document breaking changes in release notes
90+
91+
## References
92+
93+
- [JSON Schema Specification](https://json-schema.org/)
94+
- [MCP Server Schema](https://static.modelcontextprotocol.io/schemas/2025-07-09/server.schema.json)
95+
- [Model Context Protocol](https://modelcontextprotocol.io/)

0 commit comments

Comments
 (0)