@@ -69,285 +69,11 @@ jobs:
6969 - name : Validate catalog entries
7070 run : task catalog:validate
7171
72- build-and-release :
73- name : Build and Release Registry
72+ update-catalog-data :
73+ name : Update Catalog Data Files
7474 runs-on : ubuntu-latest
7575 needs : [lint, validate-and-test]
7676 if : github.ref == 'refs/heads/main' && (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch')
77- steps :
78- - name : Checkout code
79- uses : actions/checkout@v6
80- with :
81- fetch-depth : 0
82-
83- - name : Set up Go
84- uses : actions/setup-go@v6
85- with :
86- go-version-file : ' go.mod'
87- cache : true
88-
89- - name : Install Task
90- uses : arduino/setup-task@v2
91-
92- - name : Build registry files
93- run : |
94- mkdir -p dist
95- task catalog:build
96- # catalog outputs to build/<registry-name>/
97- for registry_dir in build/*/; do
98- registry_name=$(basename "$registry_dir")
99- [ -f "$registry_dir/registry.json" ] || continue
100- cp "$registry_dir/registry.json" "dist/registry.json"
101- cp "$registry_dir/official-registry.json" "dist/official-registry.json"
102- done
103- CONTAINER_COUNT=$(jq '.servers | length' dist/registry.json)
104- REMOTE_COUNT=$(jq '.remote_servers | length // 0' dist/registry.json)
105- TOTAL_COUNT=$((CONTAINER_COUNT + REMOTE_COUNT))
106- echo "Registry built successfully with $TOTAL_COUNT entries ($CONTAINER_COUNT container-based, $REMOTE_COUNT remote)"
107-
108- - name : Validate JSON files
109- run : |
110- echo "Validating ToolHive format..."
111- jq empty dist/registry.json
112- echo "Valid JSON"
113-
114- jq -e '.last_updated' dist/registry.json > /dev/null
115- echo "Has last_updated field"
116-
117- jq -e '.servers' dist/registry.json > /dev/null
118- echo "Has servers field"
119-
120- jq -e 'has("remote_servers")' dist/registry.json > /dev/null && echo "Has remote_servers field" || echo "No remote_servers field"
121-
122- jq -e '."$schema"' dist/registry.json > /dev/null
123- echo "Has schema field"
124-
125- echo ""
126- echo "Validating Official MCP format..."
127- jq empty dist/official-registry.json
128- echo "Valid JSON"
129-
130- jq -e '.version' dist/official-registry.json > /dev/null
131- echo "Has version field"
132-
133- jq -e '.meta.last_updated' dist/official-registry.json > /dev/null
134- echo "Has meta.last_updated field"
135-
136- jq -e '.data.servers' dist/official-registry.json > /dev/null
137- echo "Has data.servers field"
138-
139- SERVER_COUNT=$(jq '.data.servers | length' dist/official-registry.json)
140- if [ "$SERVER_COUNT" -gt 0 ]; then
141- jq -e '.data.servers[0].name' dist/official-registry.json > /dev/null
142- echo "Servers have name field"
143-
144- jq -e '.data.servers[0]._meta."io.modelcontextprotocol.registry/publisher-provided"' dist/official-registry.json > /dev/null
145- echo "Servers have _meta with publisher-provided extensions"
146- fi
147-
148- - name : Generate metadata
149- id : metadata
150- run : |
151- VERSION=$(date +'%Y.%m.%d')
152- TIMESTAMP=$(date +'%Y%m%d-%H%M%S')
153- echo "version=$VERSION" >> $GITHUB_OUTPUT
154- echo "timestamp=$TIMESTAMP" >> $GITHUB_OUTPUT
155-
156- CONTAINER_COUNT=$(jq '.servers | length' dist/registry.json)
157- REMOTE_COUNT=$(jq '.remote_servers | length // 0' dist/registry.json)
158- TOTAL=$((CONTAINER_COUNT + REMOTE_COUNT))
159-
160- ACTIVE_CONTAINER=$(jq '[.servers[] | select(.status == "Active")] | length' dist/registry.json)
161- ACTIVE_REMOTE=$(jq '[(.remote_servers // {})[] | select(.status == "Active")] | length' dist/registry.json)
162- ACTIVE=$((ACTIVE_CONTAINER + ACTIVE_REMOTE))
163-
164- BETA_CONTAINER=$(jq '[.servers[] | select(.status == "Beta")] | length' dist/registry.json)
165- BETA_REMOTE=$(jq '[(.remote_servers // {})[] | select(.status == "Beta")] | length' dist/registry.json)
166- BETA=$((BETA_CONTAINER + BETA_REMOTE))
167-
168- DEPRECATED_CONTAINER=$(jq '[.servers[] | select(.status == "Deprecated")] | length' dist/registry.json)
169- DEPRECATED_REMOTE=$(jq '[(.remote_servers // {})[] | select(.status == "Deprecated")] | length' dist/registry.json)
170- DEPRECATED=$((DEPRECATED_CONTAINER + DEPRECATED_REMOTE))
171-
172- OFFICIAL_CONTAINER=$(jq '[.servers[] | select(.tier == "Official")] | length' dist/registry.json)
173- OFFICIAL_REMOTE=$(jq '[(.remote_servers // {})[] | select(.tier == "Official")] | length' dist/registry.json)
174- OFFICIAL=$((OFFICIAL_CONTAINER + OFFICIAL_REMOTE))
175-
176- PARTNER_CONTAINER=$(jq '[.servers[] | select(.tier == "Partner")] | length' dist/registry.json)
177- PARTNER_REMOTE=$(jq '[(.remote_servers // {})[] | select(.tier == "Partner")] | length' dist/registry.json)
178- PARTNER=$((PARTNER_CONTAINER + PARTNER_REMOTE))
179-
180- COMMUNITY_CONTAINER=$(jq '[.servers[] | select(.tier == "Community")] | length' dist/registry.json)
181- COMMUNITY_REMOTE=$(jq '[(.remote_servers // {})[] | select(.tier == "Community")] | length' dist/registry.json)
182- COMMUNITY=$((COMMUNITY_CONTAINER + COMMUNITY_REMOTE))
183-
184- echo "total=$TOTAL" >> $GITHUB_OUTPUT
185- echo "container_count=$CONTAINER_COUNT" >> $GITHUB_OUTPUT
186- echo "remote_count=$REMOTE_COUNT" >> $GITHUB_OUTPUT
187- echo "active=$ACTIVE" >> $GITHUB_OUTPUT
188- echo "beta=$BETA" >> $GITHUB_OUTPUT
189- echo "deprecated=$DEPRECATED" >> $GITHUB_OUTPUT
190- echo "official=$OFFICIAL" >> $GITHUB_OUTPUT
191- echo "partner=$PARTNER" >> $GITHUB_OUTPUT
192- echo "community=$COMMUNITY" >> $GITHUB_OUTPUT
193-
194- - name : Create checksums
195- run : |
196- cd dist
197- sha256sum registry.json > registry.json.sha256
198- md5sum registry.json > registry.json.md5
199- sha256sum official-registry.json > official-registry.json.sha256
200- md5sum official-registry.json > official-registry.json.md5
201-
202- - name : Create tarball
203- run : |
204- cd dist
205- tar -czf registry-${{ steps.metadata.outputs.version }}.tar.gz \
206- registry.json registry.json.sha256 registry.json.md5 \
207- official-registry.json official-registry.json.sha256 official-registry.json.md5
208- tar -tzf registry-${{ steps.metadata.outputs.version }}.tar.gz
209-
210- - name : Check if release exists
211- id : check_release
212- run : |
213- if gh release view "v${{ steps.metadata.outputs.version }}" >/dev/null 2>&1; then
214- echo "exists=true" >> $GITHUB_OUTPUT
215- echo "Release v${{ steps.metadata.outputs.version }} already exists"
216- else
217- echo "exists=false" >> $GITHUB_OUTPUT
218- echo "Release v${{ steps.metadata.outputs.version }} does not exist"
219- fi
220- env :
221- GH_TOKEN : ${{ github.token }}
222-
223- - name : Get changes since last release
224- id : changes
225- if : steps.check_release.outputs.exists == 'false'
226- run : |
227- LAST_TAG=$(gh release list --limit 1 --json tagName -q '.[0].tagName' || echo "")
228-
229- if [ -z "$LAST_TAG" ]; then
230- echo "No previous release found"
231- CHANGES="Initial release"
232- else
233- echo "Last release: $LAST_TAG"
234- CHANGES=$(git log --pretty=format:"- %s" $LAST_TAG..HEAD --grep="^feat\|^fix\|^docs\|^chore" | head -20)
235-
236- if [ -z "$CHANGES" ]; then
237- CHANGES="- Minor updates and maintenance"
238- fi
239- fi
240-
241- {
242- echo "changes<<EOF"
243- echo "$CHANGES"
244- echo "EOF"
245- } >> $GITHUB_OUTPUT
246- env :
247- GH_TOKEN : ${{ github.token }}
248-
249- - name : Create Release
250- if : steps.check_release.outputs.exists == 'false'
251- uses : ncipollo/release-action@v1
252- with :
253- tag : v${{ steps.metadata.outputs.version }}
254- name : Registry v${{ steps.metadata.outputs.version }}
255- body : |
256- ## ToolHive Registry Snapshot
257-
258- **Date**: ${{ steps.metadata.outputs.version }}
259- **Build**: ${{ steps.metadata.outputs.timestamp }}
260-
261- ### Statistics
262-
263- | Category | Count |
264- |----------|-------|
265- | **Total Servers** | ${{ steps.metadata.outputs.total }} |
266- | **Container-based** | ${{ steps.metadata.outputs.container_count }} |
267- | **Remote** | ${{ steps.metadata.outputs.remote_count }} |
268- | **Active** | ${{ steps.metadata.outputs.active }} |
269- | **Beta** | ${{ steps.metadata.outputs.beta }} |
270- | **Deprecated** | ${{ steps.metadata.outputs.deprecated }} |
271-
272- | Tier | Count |
273- |------|-------|
274- | **Official** | ${{ steps.metadata.outputs.official }} |
275- | **Partner** | ${{ steps.metadata.outputs.partner }} |
276- | **Community** | ${{ steps.metadata.outputs.community }} |
277-
278- ### Download Options
279-
280- **Individual Files:**
281- - **registry.json** - ToolHive format registry file
282- - **official-registry.json** - Official MCP format registry file
283-
284- **Archives:**
285- - **registry-${{ steps.metadata.outputs.version }}.tar.gz** - Complete archive with both formats and checksums
286-
287- ### Direct URLs
288-
289- **ToolHive Format:**
290- - Latest: `https://github.com/stacklok/toolhive-registry/releases/latest/download/registry.json`
291- - This version: `https://github.com/stacklok/toolhive-registry/releases/download/v${{ steps.metadata.outputs.version }}/registry.json`
292-
293- **Official MCP Format:**
294- - Latest: `https://github.com/stacklok/toolhive-registry/releases/latest/download/official-registry.json`
295- - This version: `https://github.com/stacklok/toolhive-registry/releases/download/v${{ steps.metadata.outputs.version }}/official-registry.json`
296-
297- ### Recent Changes
298-
299- ${{ steps.changes.outputs.changes }}
300-
301- ---
302- *This is an automated release generated from the main branch.*
303- artifacts : |
304- dist/registry.json
305- dist/registry.json.sha256
306- dist/registry.json.md5
307- dist/official-registry.json
308- dist/official-registry.json.sha256
309- dist/official-registry.json.md5
310- dist/registry-${{ steps.metadata.outputs.version }}.tar.gz
311- makeLatest : true
312- artifactErrorsFailBuild : true
313-
314- - name : Update existing release
315- if : steps.check_release.outputs.exists == 'true'
316- run : |
317- echo "Updating existing release v${{ steps.metadata.outputs.version }}"
318-
319- # Clean up legacy catalog-* prefixed assets from previous dual-pipeline builds
320- for asset in $(gh release view "v${{ steps.metadata.outputs.version }}" --json assets -q '.assets[].name | select(startswith("catalog-"))'); do
321- gh release delete-asset "v${{ steps.metadata.outputs.version }}" "$asset" --yes || true
322- done
323-
324- gh release delete-asset "v${{ steps.metadata.outputs.version }}" registry.json --yes || true
325- gh release delete-asset "v${{ steps.metadata.outputs.version }}" registry.json.sha256 --yes || true
326- gh release delete-asset "v${{ steps.metadata.outputs.version }}" registry.json.md5 --yes || true
327- gh release delete-asset "v${{ steps.metadata.outputs.version }}" official-registry.json --yes || true
328- gh release delete-asset "v${{ steps.metadata.outputs.version }}" official-registry.json.sha256 --yes || true
329- gh release delete-asset "v${{ steps.metadata.outputs.version }}" official-registry.json.md5 --yes || true
330- gh release delete-asset "v${{ steps.metadata.outputs.version }}" "registry-${{ steps.metadata.outputs.version }}.tar.gz" --yes || true
331-
332- gh release upload "v${{ steps.metadata.outputs.version }}" \
333- dist/registry.json \
334- dist/registry.json.sha256 \
335- dist/registry.json.md5 \
336- dist/official-registry.json \
337- dist/official-registry.json.sha256 \
338- dist/official-registry.json.md5 \
339- "dist/registry-${{ steps.metadata.outputs.version }}.tar.gz" \
340- --clobber
341-
342- echo "Release updated successfully"
343- env :
344- GH_TOKEN : ${{ github.token }}
345-
346- open-dist-pr :
347- name : Open PR with Built Registry Files
348- runs-on : ubuntu-latest
349- needs : [build-and-release]
350- if : github.ref == 'refs/heads/main' && (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch')
35177 steps :
35278 - name : Checkout code
35379 uses : actions/checkout@v6
@@ -376,20 +102,23 @@ jobs:
376102 id : metadata
377103 run : echo "version=$(date +'%Y.%m.%d')" >> $GITHUB_OUTPUT
378104
379- - name : Open PR with built registry files
105+ - name : Open or update PR with catalog data files
380106 uses : peter-evans/create-pull-request@v8
381107 with :
382108 token : ${{ secrets.GITHUB_TOKEN }}
383- commit-message : ' chore: update built registry files for v${{ steps.metadata.outputs.version }}'
384- title : ' chore: update built registry files for v${{ steps.metadata.outputs.version }}'
109+ commit-message : ' chore: update catalog data files for v${{ steps.metadata.outputs.version }}'
110+ title : ' chore: update catalog data files for v${{ steps.metadata.outputs.version }}'
385111 body : |
386- Automated PR to commit the built registry files for release `v${{ steps.metadata.outputs.version }}`.
112+ Automated PR to update the embedded catalog data files for `v${{ steps.metadata.outputs.version }}`.
387113
388114 Files updated:
389115 - `pkg/catalog/toolhive/data/registry.json` — ToolHive legacy format
390116 - `pkg/catalog/toolhive/data/official-registry.json` — Upstream MCP format
391- branch : update-dist-${{ steps.metadata.outputs.version }}
117+
118+ When merged, a release will be created automatically.
119+ branch : update-catalog-data-${{ steps.metadata.outputs.version }}
392120 delete-branch : true
121+ auto-merge : true
393122 add-paths : |
394123 pkg/catalog/*/data/registry.json
395124 pkg/catalog/*/data/official-registry.json
@@ -415,23 +144,43 @@ jobs:
415144 run : task catalog:build
416145
417146 - name : Generate PR comment
147+ id : comment
418148 run : |
419149 CONTAINER_COUNT=$(jq '.servers | length' build/toolhive/registry.json)
420150 REMOTE_COUNT=$(jq '.remote_servers | length // 0' build/toolhive/registry.json)
421151 TOTAL=$((CONTAINER_COUNT + REMOTE_COUNT))
422152 SIZE=$(du -h build/toolhive/registry.json | cut -f1)
153+ LAST_UPDATED=$(jq -r '.last_updated' build/toolhive/registry.json)
423154
424- echo "## Registry Build Preview" > pr-comment.md
425- echo "" >> pr-comment.md
426- echo "Registry built successfully!" >> pr-comment.md
427- echo "" >> pr-comment.md
428- echo "- **Total Servers**: $TOTAL" >> pr-comment.md
429- echo " - Container-based: $CONTAINER_COUNT" >> pr-comment.md
430- echo " - Remote: $REMOTE_COUNT" >> pr-comment.md
431- echo "- **File Size**: $SIZE" >> pr-comment.md
432- echo "- **Last Updated**: $(jq -r '.last_updated' build/toolhive/registry.json)" >> pr-comment.md
433- echo "" >> pr-comment.md
434- echo "The registry files will be published when this PR is merged." >> pr-comment.md
155+ {
156+ echo "body<<EOF"
157+ echo "## Registry Build Preview"
158+ echo ""
159+ echo "Registry built successfully!"
160+ echo ""
161+ echo "- **Total Servers**: $TOTAL"
162+ echo " - Container-based: $CONTAINER_COUNT"
163+ echo " - Remote: $REMOTE_COUNT"
164+ echo "- **File Size**: $SIZE"
165+ echo "- **Last Updated**: $LAST_UPDATED"
166+ echo "EOF"
167+ } >> $GITHUB_OUTPUT
168+
169+ - name : Find existing comment
170+ id : find-comment
171+ uses : peter-evans/find-comment@v3
172+ with :
173+ issue-number : ${{ github.event.pull_request.number }}
174+ comment-author : github-actions[bot]
175+ body-includes : Registry Build Preview
176+
177+ - name : Post or update PR comment
178+ uses : peter-evans/create-or-update-comment@v4
179+ with :
180+ comment-id : ${{ steps.find-comment.outputs.comment-id }}
181+ issue-number : ${{ github.event.pull_request.number }}
182+ body : ${{ steps.comment.outputs.body }}
183+ edit-mode : replace
435184
436185 - name : Upload PR artifact
437186 uses : actions/upload-artifact@v6
0 commit comments