Skip to content

Commit 85d6d2a

Browse files
authored
Merge pull request #88 from stacklok/add-official
2 parents f53bead + 04ffa53 commit 85d6d2a

File tree

14 files changed

+1429
-320
lines changed

14 files changed

+1429
-320
lines changed

.github/workflows/build-and-publish.yml

Lines changed: 61 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -91,18 +91,21 @@ jobs:
9191
- name: Build registry-builder
9292
run: go build -o registry-builder ./cmd/registry-builder
9393

94-
- name: Build registry.json
94+
- name: Build registry files (both formats)
9595
run: |
9696
mkdir -p dist
97-
./registry-builder build -v
97+
./registry-builder build --format all -v
9898
cp build/registry.json dist/registry.json
99+
cp build/official-registry.json dist/official-registry.json
99100
CONTAINER_COUNT=$(jq '.servers | length' dist/registry.json)
100101
REMOTE_COUNT=$(jq '.remote_servers | length // 0' dist/registry.json)
101102
TOTAL_COUNT=$((CONTAINER_COUNT + REMOTE_COUNT))
102103
echo "Registry built successfully with $TOTAL_COUNT entries ($CONTAINER_COUNT container-based, $REMOTE_COUNT remote)"
104+
echo "Both ToolHive and official MCP formats generated"
103105
104-
- name: Validate JSON
106+
- name: Validate JSON files
105107
run: |
108+
echo "Validating ToolHive format..."
106109
# Validate JSON structure
107110
jq empty dist/registry.json
108111
echo "✅ Valid JSON"
@@ -119,6 +122,35 @@ jobs:
119122
120123
jq -e '."$schema"' dist/registry.json > /dev/null
121124
echo "✅ Has schema field"
125+
126+
echo ""
127+
echo "Validating Official MCP format..."
128+
# Validate official registry JSON structure
129+
jq empty dist/official-registry.json
130+
echo "✅ Valid JSON"
131+
132+
# Check required fields for official format
133+
jq -e '.version' dist/official-registry.json > /dev/null
134+
echo "✅ Has version field"
135+
136+
jq -e '.meta.last_updated' dist/official-registry.json > /dev/null
137+
echo "✅ Has meta.last_updated field"
138+
139+
jq -e '.data.servers' dist/official-registry.json > /dev/null
140+
echo "✅ Has data.servers field"
141+
142+
# Check that servers have the flattened structure with _meta
143+
SERVER_COUNT=$(jq '.data.servers | length' dist/official-registry.json)
144+
if [ "$SERVER_COUNT" -gt 0 ]; then
145+
jq -e '.data.servers[0].name' dist/official-registry.json > /dev/null
146+
echo "✅ Servers have name field"
147+
148+
jq -e '.data.servers[0]._meta.publisher' dist/official-registry.json > /dev/null
149+
echo "✅ Servers have _meta.publisher field"
150+
151+
jq -e '.data.servers[0]._meta."io.modelcontextprotocol.registry"' dist/official-registry.json > /dev/null
152+
echo "✅ Servers have registry extensions"
153+
fi
122154
123155
- name: Generate metadata
124156
id: metadata
@@ -175,11 +207,15 @@ jobs:
175207
cd dist
176208
sha256sum registry.json > registry.json.sha256
177209
md5sum registry.json > registry.json.md5
210+
sha256sum official-registry.json > official-registry.json.sha256
211+
md5sum official-registry.json > official-registry.json.md5
178212
179213
- name: Create tarball
180214
run: |
181215
cd dist
182-
tar -czf registry-${{ steps.metadata.outputs.version }}.tar.gz registry.json registry.json.sha256 registry.json.md5
216+
tar -czf registry-${{ steps.metadata.outputs.version }}.tar.gz \
217+
registry.json registry.json.sha256 registry.json.md5 \
218+
official-registry.json official-registry.json.sha256 official-registry.json.md5
183219
tar -tzf registry-${{ steps.metadata.outputs.version }}.tar.gz
184220
185221
- name: Check if release exists
@@ -251,16 +287,22 @@ jobs:
251287
252288
### 📥 Download Options
253289
254-
- **registry.json** - The complete registry file
255-
- **registry-${{ steps.metadata.outputs.version }}.tar.gz** - Archive with checksums
290+
**Individual Files:**
291+
- **registry.json** - ToolHive format registry file
292+
- **official-registry.json** - Official MCP format registry file
293+
294+
**Archives:**
295+
- **registry-${{ steps.metadata.outputs.version }}.tar.gz** - Complete archive with both formats and checksums
256296
257297
### 🔗 Direct URLs
258298
259-
Latest registry is always available at:
260-
- `https://github.com/stacklok/toolhive-registry/releases/latest/download/registry.json`
299+
**ToolHive Format:**
300+
- Latest: `https://github.com/stacklok/toolhive-registry/releases/latest/download/registry.json`
301+
- This version: `https://github.com/stacklok/toolhive-registry/releases/download/v${{ steps.metadata.outputs.version }}/registry.json`
261302
262-
This specific version:
263-
- `https://github.com/stacklok/toolhive-registry/releases/download/v${{ steps.metadata.outputs.version }}/registry.json`
303+
**Official MCP Format:**
304+
- Latest: `https://github.com/stacklok/toolhive-registry/releases/latest/download/official-registry.json`
305+
- This version: `https://github.com/stacklok/toolhive-registry/releases/download/v${{ steps.metadata.outputs.version }}/official-registry.json`
264306
265307
### 📝 Recent Changes
266308
@@ -272,6 +314,9 @@ jobs:
272314
dist/registry.json
273315
dist/registry.json.sha256
274316
dist/registry.json.md5
317+
dist/official-registry.json
318+
dist/official-registry.json.sha256
319+
dist/official-registry.json.md5
275320
dist/registry-${{ steps.metadata.outputs.version }}.tar.gz
276321
makeLatest: true
277322
artifactErrorsFailBuild: true
@@ -285,13 +330,19 @@ jobs:
285330
gh release delete-asset "v${{ steps.metadata.outputs.version }}" registry.json --yes || true
286331
gh release delete-asset "v${{ steps.metadata.outputs.version }}" registry.json.sha256 --yes || true
287332
gh release delete-asset "v${{ steps.metadata.outputs.version }}" registry.json.md5 --yes || true
333+
gh release delete-asset "v${{ steps.metadata.outputs.version }}" official-registry.json --yes || true
334+
gh release delete-asset "v${{ steps.metadata.outputs.version }}" official-registry.json.sha256 --yes || true
335+
gh release delete-asset "v${{ steps.metadata.outputs.version }}" official-registry.json.md5 --yes || true
288336
gh release delete-asset "v${{ steps.metadata.outputs.version }}" "registry-${{ steps.metadata.outputs.version }}.tar.gz" --yes || true
289337
290338
# Upload new assets
291339
gh release upload "v${{ steps.metadata.outputs.version }}" \
292340
dist/registry.json \
293341
dist/registry.json.sha256 \
294342
dist/registry.json.md5 \
343+
dist/official-registry.json \
344+
dist/official-registry.json.sha256 \
345+
dist/official-registry.json.md5 \
295346
"dist/registry-${{ steps.metadata.outputs.version }}.tar.gz" \
296347
--clobber
297348

Taskfile.yml

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -152,16 +152,41 @@ tasks:
152152
- ./{{.BUILD_DIR}}/registry-builder list -v
153153

154154
build:registry:
155-
desc: Build the registry JSON from YAML files
155+
desc: Build registry files in both ToolHive and official MCP formats
156156
deps: [build:registry-builder]
157157
cmds:
158-
- echo "🏗️ Building registry.json..."
159-
- ./{{.BUILD_DIR}}/registry-builder build -v
158+
- echo "🏗️ Building registry files (both formats)..."
159+
- ./{{.BUILD_DIR}}/registry-builder build --format all -v
160160
sources:
161161
- "{{.REGISTRY_DIR}}/**/*.yaml"
162162
- "{{.REGISTRY_DIR}}/**/*.yml"
163163
generates:
164164
- "{{.BUILD_DIR}}/registry.json"
165+
- "{{.BUILD_DIR}}/official-registry.json"
166+
167+
build:registry:toolhive:
168+
desc: Build registry in ToolHive format only
169+
deps: [build:registry-builder]
170+
cmds:
171+
- echo "🏗️ Building ToolHive registry.json..."
172+
- ./{{.BUILD_DIR}}/registry-builder build --format toolhive -v
173+
sources:
174+
- "{{.REGISTRY_DIR}}/**/*.yaml"
175+
- "{{.REGISTRY_DIR}}/**/*.yml"
176+
generates:
177+
- "{{.BUILD_DIR}}/registry.json"
178+
179+
build:registry:official:
180+
desc: Build registry in official MCP format only
181+
deps: [build:registry-builder]
182+
cmds:
183+
- echo "🏗️ Building official MCP registry..."
184+
- ./{{.BUILD_DIR}}/registry-builder build --format official-mcp-registry -v
185+
sources:
186+
- "{{.REGISTRY_DIR}}/**/*.yaml"
187+
- "{{.REGISTRY_DIR}}/**/*.yml"
188+
generates:
189+
- "{{.BUILD_DIR}}/official-registry.json"
165190

166191
test:
167192
desc: Run tests
@@ -212,7 +237,9 @@ tasks:
212237
- task: validate
213238
- task: build:registry
214239
- echo "✨ Full cycle complete!"
215-
- 'echo "Registry JSON available at: {{.BUILD_DIR}}/registry.json"'
240+
- 'echo "Registry files available at:"'
241+
- 'echo " - ToolHive format: {{.BUILD_DIR}}/registry.json"'
242+
- 'echo " - Official MCP format: {{.BUILD_DIR}}/official-registry.json"'
216243

217244
quick-start:
218245
desc: Quick start for new users
@@ -225,7 +252,7 @@ tasks:
225252
- 'echo " 1. Review imported entries in {{.REGISTRY_DIR}}/"'
226253
- echo " 2. Add new entries by creating directories with spec.yaml files"
227254
- echo " 3. Run 'task validate' to check your changes"
228-
- echo " 4. Run 'task build:registry' to generate the final JSON"
255+
- echo " 4. Run 'task build:registry' to generate registry files (both formats)"
229256

230257
version:
231258
desc: Show version information

cmd/registry-builder/main.go

Lines changed: 43 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ import (
1414
"github.com/stacklok/toolhive-registry/pkg/types"
1515
)
1616

17+
const (
18+
RegistryToolHiveFormat = "toolhive"
19+
RegistryOfficialMCPRegistry = "official-mcp-registry"
20+
RegistryAllFormats = "all"
21+
)
22+
1723
var (
1824
// Version information (set during build)
1925
version = "dev"
@@ -80,7 +86,8 @@ func init() {
8086

8187
// Build command flags
8288
buildCmd.Flags().StringVarP(&outputDir, "output-dir", "o", "build", "Output directory for built registry files")
83-
buildCmd.Flags().StringVarP(&outputFormat, "format", "f", "toolhive", "Output format (toolhive, mcp-registry, all)")
89+
buildCmd.Flags().StringVarP(&outputFormat, "format", "f", "toolhive",
90+
fmt.Sprintf("Output format (%s, %s, %s)", RegistryToolHiveFormat, RegistryOfficialMCPRegistry, RegistryAllFormats))
8491

8592
// Add commands
8693
rootCmd.AddCommand(buildCmd)
@@ -150,34 +157,52 @@ func runBuild(_ *cobra.Command, _ []string) error {
150157

151158
func determineFormats(format string) []string {
152159
switch strings.ToLower(format) {
153-
case "all":
160+
case RegistryAllFormats:
154161
// Return all supported formats
155-
// For now, just toolhive, but will expand to include mcp-registry
156-
return []string{"toolhive"}
157-
case "mcp-registry", "mcp":
158-
// Future: Upstream MCP Registry format
159-
fmt.Println("Note: MCP Registry format support is planned for a future release")
160-
fmt.Println("This will generate output compatible with https://github.com/modelcontextprotocol/registry")
161-
return []string{}
162-
case "toolhive":
163-
fallthrough
162+
return []string{RegistryToolHiveFormat, RegistryOfficialMCPRegistry}
163+
case RegistryOfficialMCPRegistry:
164+
return []string{RegistryOfficialMCPRegistry}
165+
case RegistryToolHiveFormat:
166+
return []string{RegistryToolHiveFormat}
164167
default:
165-
return []string{"toolhive"}
168+
return []string{RegistryToolHiveFormat}
166169
}
167170
}
168171

169172
func buildFormat(loader *registry.Loader, format string, outputDir string) error {
170173
switch format {
171-
case "toolhive":
174+
case RegistryToolHiveFormat:
172175
return buildToolhiveFormat(loader, outputDir)
173-
case "mcp-registry":
174-
// Future implementation
175-
return fmt.Errorf("MCP Registry format not yet implemented")
176+
case RegistryOfficialMCPRegistry:
177+
return buildOfficialMCPRegistryFormat(loader, outputDir)
176178
default:
177179
return fmt.Errorf("unknown format: %s", format)
178180
}
179181
}
180182

183+
func buildOfficialMCPRegistryFormat(loader *registry.Loader, outputDir string) error {
184+
// Create official MCP Registry builder
185+
r := registry.NewOfficialRegistry(loader)
186+
187+
// Ensure output directory exists
188+
if err := os.MkdirAll(outputDir, 0750); err != nil {
189+
return fmt.Errorf("failed to create output directory: %w", err)
190+
}
191+
192+
// TODO: Add validation step
193+
194+
// Write JSON output
195+
outputPath := filepath.Join(outputDir, "official-registry.json")
196+
if err := r.WriteJSON(outputPath); err != nil {
197+
return fmt.Errorf("failed to write output: %w", err)
198+
}
199+
200+
if verbose {
201+
log.Printf("Written Official MCP Registry format to %s", outputPath)
202+
}
203+
return nil
204+
}
205+
181206
func buildToolhiveFormat(loader *registry.Loader, outputDir string) error {
182207
// Create builder
183208
builder := registry.NewBuilder(loader)
@@ -205,14 +230,6 @@ func buildToolhiveFormat(loader *registry.Loader, outputDir string) error {
205230
return nil
206231
}
207232

208-
// Future: buildMCPRegistryFormat function will be added here
209-
// func buildMCPRegistryFormat(loader *registry.Loader, outputDir string) error {
210-
// // Implementation for upstream MCP Registry format
211-
// // This will create output compatible with the MCP Registry service:
212-
// // https://github.com/modelcontextprotocol/registry
213-
// // The format will evolve as the upstream standard evolves
214-
// }
215-
216233
func runValidate(_ *cobra.Command, _ []string) error {
217234
if verbose {
218235
log.Printf("Validating registry entries in %s", registryPath)
@@ -327,15 +344,15 @@ func displayEntry(entry *types.RegistryEntry, verbose bool) {
327344
func getEntryStatus(entry *types.RegistryEntry) string {
328345
status := entry.GetStatus()
329346
if status == "" {
330-
status = "Active"
347+
status = types.StatusActive
331348
}
332349
return status
333350
}
334351

335352
func getEntryTier(entry *types.RegistryEntry) string {
336353
tier := entry.GetTier()
337354
if tier == "" {
338-
tier = "Community"
355+
tier = types.TierCommunity
339356
}
340357
return tier
341358
}

go.mod

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ go 1.24.5
44

55
require (
66
github.com/google/go-cmp v0.7.0
7+
github.com/google/uuid v1.6.0
8+
github.com/modelcontextprotocol/registry v0.0.0-20250901215855-07d353ba9295
79
github.com/spf13/cobra v1.10.1
810
github.com/stacklok/toolhive v0.2.15
911
github.com/stretchr/testify v1.11.1
@@ -80,7 +82,6 @@ require (
8082
github.com/google/certificate-transparency-go v1.3.2 // indirect
8183
github.com/google/go-containerregistry v0.20.6 // indirect
8284
github.com/google/s2a-go v0.1.9 // indirect
83-
github.com/google/uuid v1.6.0 // indirect
8485
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
8586
github.com/googleapis/gax-go/v2 v2.15.0 // indirect
8687
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect
@@ -133,6 +134,9 @@ require (
133134
github.com/transparency-dev/merkle v0.0.2 // indirect
134135
github.com/transparency-dev/tessera v1.0.0-rc2 // indirect
135136
github.com/vbatts/tar-split v0.12.1 // indirect
137+
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
138+
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
139+
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
136140
github.com/zalando/go-keyring v0.2.6 // indirect
137141
go.mongodb.org/mongo-driver v1.17.3 // indirect
138142
go.opencensus.io v0.24.0 // indirect

go.sum

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1112,6 +1112,8 @@ github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7z
11121112
github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko=
11131113
github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=
11141114
github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc=
1115+
github.com/modelcontextprotocol/registry v0.0.0-20250901215855-07d353ba9295 h1:g9ffircTjdGMjVEsR0HDKwDPlYyVRadeIJZYySJnSWo=
1116+
github.com/modelcontextprotocol/registry v0.0.0-20250901215855-07d353ba9295/go.mod h1:chWjwR39pUI2XPlNhzdFM+v1BFG2JdaCczUOoq7CzT8=
11151117
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
11161118
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
11171119
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
@@ -1271,6 +1273,12 @@ github.com/transparency-dev/tessera v1.0.0-rc2 h1:BKtDWr0nhL9dG66cS4DyKU9lpZFbUZ
12711273
github.com/transparency-dev/tessera v1.0.0-rc2/go.mod h1:aaLlvG/sEPMzT96iIF4hua6Z9pLzkfDtkbaUAR4IL8I=
12721274
github.com/vbatts/tar-split v0.12.1 h1:CqKoORW7BUWBe7UL/iqTVvkTBOF8UvOMKOIZykxnnbo=
12731275
github.com/vbatts/tar-split v0.12.1/go.mod h1:eF6B6i6ftWQcDqEn3/iGFRFRo8cBIMSJVOpnNdfTMFA=
1276+
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
1277+
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
1278+
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
1279+
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
1280+
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
1281+
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
12741282
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
12751283
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
12761284
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=

0 commit comments

Comments
 (0)