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
d7d4854
fix: decouple NuGet indexing from release
rodri-oliveira-dev Oct 1, 2026
6ffc2d3
feat: add standalone MCP registry publication workflow
rodri-oliveira-dev Oct 1, 2026
376356c
docs: clarify asynchronous NuGet indexing
rodri-oliveira-dev Oct 1, 2026
99de847
docs: clarify asynchronous NuGet indexing in Portuguese
rodri-oliveira-dev Oct 1, 2026
9503790
docs: document standalone MCP registry publishing
rodri-oliveira-dev Oct 1, 2026
a8e3da3
docs: document standalone MCP registry publishing in Portuguese
rodri-oliveira-dev Oct 1, 2026
6ca648d
fix: shorten MCP registry description
rodri-oliveira-dev Oct 1, 2026
4ae9932
fix: allow registry metadata correction after release
rodri-oliveira-dev Oct 1, 2026
f59816c
docs: allow registry metadata-only corrections
rodri-oliveira-dev Oct 1, 2026
1e36534
docs: allow registry metadata-only corrections in Portuguese
rodri-oliveira-dev Oct 1, 2026
269040a
refactor: split release into governed stages
rodri-oliveira-dev Oct 1, 2026
5d5c935
fix: harden staged release tag verification
rodri-oliveira-dev Oct 1, 2026
ce2c53f
docs: document staged package publication
rodri-oliveira-dev Oct 1, 2026
9631369
docs: document staged package publication in Portuguese
rodri-oliveira-dev Oct 1, 2026
da84a61
docs: include GitHub Packages in release flow
rodri-oliveira-dev Oct 1, 2026
d6c4fe6
docs: include GitHub Packages in release flow in Portuguese
rodri-oliveira-dev Oct 1, 2026
86dd5ee
docs: align detailed release walkthrough
rodri-oliveira-dev Oct 1, 2026
a21145e
docs: align detailed release walkthrough in Portuguese
rodri-oliveira-dev Oct 1, 2026
65376f0
docs: explain registry metadata source
rodri-oliveira-dev Oct 1, 2026
2fe0eaf
fix: generate release notes without command substitution
rodri-oliveira-dev Oct 1, 2026
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
206 changes: 206 additions & 0 deletions .github/workflows/publish-mcp-registry.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
name: Publish Repo2C4 MCP Registry metadata

run-name: "Publish Repo2C4 MCP ${{ inputs.version }} to Official Registry"

on:
workflow_dispatch:
inputs:
version:
description: "Released semantic version without the v prefix, for example 1.0.0."
required: true
type: string
confirmation:
description: "Type PUBLISH-MCP to publish the released MCP metadata."
required: true
type: string

permissions:
contents: read

concurrency:
group: "mcp-registry-${{ inputs.version }}"
cancel-in-progress: false

jobs:
publish-mcp-registry:
name: Publish Repo2C4 MCP to Official Registry
runs-on: ubuntu-latest
environment: release
permissions:
contents: read
id-token: write

steps:
- name: Validate request and resolve released commit
id: release
shell: bash
env:
GH_TOKEN: ${{ github.token }}
VERSION: ${{ inputs.version }}
CONFIRMATION: ${{ inputs.confirmation }}
run: |
set -euo pipefail

test "$GITHUB_REF" = refs/heads/main

if [[ ! "$VERSION" =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$ ]]; then
echo "::error::Invalid semantic version '$VERSION'."
exit 1
fi

if [ "$CONFIRMATION" != "PUBLISH-MCP" ]; then
echo "::error::Confirmation must be PUBLISH-MCP."
exit 1
fi

tag="v$VERSION"
tag_json="$(gh api "repos/$GITHUB_REPOSITORY/git/ref/tags/$tag")"
tag_type="$(jq -r '.object.type' <<<"$tag_json")"
tag_sha="$(jq -r '.object.sha' <<<"$tag_json")"

while [ "$tag_type" = "tag" ]; do
tag_object_json="$(gh api "repos/$GITHUB_REPOSITORY/git/tags/$tag_sha")"
tag_type="$(jq -r '.object.type' <<<"$tag_object_json")"
tag_sha="$(jq -r '.object.sha' <<<"$tag_object_json")"
done

if [ "$tag_type" != "commit" ]; then
echo "::error::Release tag $tag does not resolve to a commit."
exit 1
fi

release_json="$(gh release view "$tag" --repo "$GITHUB_REPOSITORY" --json isDraft,tagName)"
if [ "$(jq -r '.isDraft' <<<"$release_json")" != "false" ]; then
echo "::error::GitHub Release $tag is still a draft."
exit 1
fi
if [ "$(jq -r '.tagName' <<<"$release_json")" != "$tag" ]; then
echo "::error::GitHub Release tag does not match $tag."
exit 1
fi

echo "commit=$tag_sha" >> "$GITHUB_OUTPUT"
echo "Released commit: $tag_sha"

- name: Checkout released package source
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
ref: ${{ steps.release.outputs.commit }}
path: released-source
persist-credentials: false
show-progress: false

# Package code is pinned to the released tag above. Registry metadata intentionally
# comes from the current trusted main commit so metadata-only fixes can be published
# after release without rebuilding or republishing the immutable NuGet package.
# The workflow still requires main and validates name/version/package binding below.
- name: Checkout current registry metadata from main
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
ref: ${{ github.sha }}
path: registry-metadata
persist-credentials: false
show-progress: false

- name: Set up .NET 10
uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6
with:
dotnet-version: '10.0.x'

- name: Verify registry metadata and NuGet availability
shell: bash
env:
VERSION: ${{ inputs.version }}
run: |
set -euo pipefail
metadata="registry-metadata/server.json"
test "$(jq -r '.name' "$metadata")" = "io.github.rodri-oliveira-dev/repo2c4-mcp"
test "$(jq -r '.version' "$metadata")" = "$VERSION"
test "$(jq -r '.packages | length' "$metadata")" -eq 1
test "$(jq -r '.packages[0].registryType' "$metadata")" = "nuget"
test "$(jq -r '.packages[0].identifier' "$metadata")" = "Repo2C4.Mcp"
test "$(jq -r '.packages[0].version' "$metadata")" = "$VERSION"
test "$(jq -r '.packages[0].runtimeHint' "$metadata")" = "dnx"
test "$(jq -r '.packages[0].transport.type' "$metadata")" = "stdio"
grep -Fq '<!-- mcp-name: io.github.rodri-oliveira-dev/repo2c4-mcp -->' released-source/src/Repo2C4.Mcp/README.md

work="$(mktemp -d)"
trap 'rm -rf "$work"' EXIT
for attempt in {1..12}; do
if dotnet tool install --tool-path "$work/tool" Repo2C4.Mcp --version "$VERSION" \
--add-source https://api.nuget.org/v3/index.json \
--ignore-failed-sources >/dev/null 2>&1; then
break
fi
if [ "$attempt" -eq 12 ]; then
echo "::error::Repo2C4.Mcp $VERSION is not yet consumable from NuGet.org. The release is already complete; rerun this registry workflow after NuGet indexing finishes."
exit 1
fi
sleep 10
done

"$work/tool/repo2c4-mcp" --help >"$work/stdout" 2>"$work/stderr"
test ! -s "$work/stdout"
grep -Fq 'stdout is reserved exclusively for MCP protocol messages' "$work/stderr"

- name: Install pinned mcp-publisher
shell: bash
env:
MCP_PUBLISHER_VERSION: '1.8.1'
MCP_PUBLISHER_SHA256: 'a06c9096dcb9727c13555b6be26c7effa707b01f06a4c561ba7a3635443cf2cc'
run: |
set -euo pipefail
archive="$RUNNER_TEMP/mcp-publisher.tar.gz"
curl --fail --location --silent --show-error \
--proto '=https' --proto-redir '=https' \
"https://github.com/modelcontextprotocol/registry/releases/download/v$MCP_PUBLISHER_VERSION/mcp-publisher_linux_amd64.tar.gz" \
--output "$archive"
echo "$MCP_PUBLISHER_SHA256 $archive" | sha256sum --check -
tar -xzf "$archive" -C "$RUNNER_TEMP" mcp-publisher
chmod +x "$RUNNER_TEMP/mcp-publisher"
"$RUNNER_TEMP/mcp-publisher" --help >/dev/null

- name: Validate and publish MCP registry metadata
shell: bash
env:
VERSION: ${{ inputs.version }}
run: |
set -euo pipefail
publisher="$RUNNER_TEMP/mcp-publisher"
registry="https://registry.modelcontextprotocol.io"
server_name="io.github.rodri-oliveira-dev%2Frepo2c4-mcp"
detail_url="$registry/v0.1/servers/$server_name/versions/$VERSION"

metadata="registry-metadata/server.json"
"$publisher" validate "$metadata"

status="$(curl --silent --show-error --proto '=https' --proto-redir '=https' \
--output "$RUNNER_TEMP/registry-existing.json" \
--write-out '%{http_code}' "$detail_url")"
if [ "$status" = "200" ]; then
echo "Repo2C4 MCP $VERSION is already present in the Official MCP Registry."
exit 0
fi
if [ "$status" != "404" ]; then
cat "$RUNNER_TEMP/registry-existing.json" >&2 || true
echo "::error::Unexpected MCP Registry lookup status: $status"
exit 1
fi

"$publisher" login github-oidc
"$publisher" publish "$metadata"

for attempt in {1..12}; do
status="$(curl --silent --show-error --proto '=https' --proto-redir '=https' \
--output "$RUNNER_TEMP/registry-published.json" \
--write-out '%{http_code}' "$detail_url")"
if [ "$status" = "200" ]; then
echo "Published Repo2C4 MCP $VERSION to the Official MCP Registry."
exit 0
fi
sleep 5
done

cat "$RUNNER_TEMP/registry-published.json" >&2 || true
echo "::error::MCP Registry publication was not observable after publishing."
exit 1
Loading
Loading