Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
191 changes: 191 additions & 0 deletions .github/workflows/publish-mcp-registry.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
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 the exact released commit
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
ref: ${{ steps.release.outputs.commit }}
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
test "$(jq -r '.name' server.json)" = "io.github.rodri-oliveira-dev/repo2c4-mcp"
test "$(jq -r '.version' server.json)" = "$VERSION"
test "$(jq -r '.packages | length' server.json)" -eq 1
test "$(jq -r '.packages[0].registryType' server.json)" = "nuget"
test "$(jq -r '.packages[0].identifier' server.json)" = "Repo2C4.Mcp"
test "$(jq -r '.packages[0].version' server.json)" = "$VERSION"
test "$(jq -r '.packages[0].runtimeHint' server.json)" = "dnx"
test "$(jq -r '.packages[0].transport.type' server.json)" = "stdio"
grep -Fq '<!-- mcp-name: io.github.rodri-oliveira-dev/repo2c4-mcp -->' 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"

"$publisher" validate server.json

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 server.json

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
173 changes: 1 addition & 172 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ jobs:
'~~~' \
"" \
"The attached .nupkg files are the exact validated payload published to NuGet.org." \
"Repo2C4.Mcp registry metadata is versioned in server.json and is published to the Official MCP Registry after NuGet consumer verification." \
"Repo2C4.Mcp registry metadata is versioned in server.json and is published separately to the Official MCP Registry after the matching NuGet package becomes publicly consumable." \
"Verify downloads with SHA256SUMS. See docs/distribution.md for runtime requirements, MCP configuration, registry publication and security guidance." \
> "$notes_file"
gh release create "$tag" \
Expand Down Expand Up @@ -335,174 +335,3 @@ jobs:
echo "::error::Published release assets differ from the verified payload."
exit 1
fi


post-publish:
name: Verify public consumer installation
needs: publish
if: ${{ needs.publish.result == 'success' }}
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Set up .NET 10
uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6
with:
dotnet-version: '10.0.x'

- name: Install and exercise the published NuGet tools
shell: bash
env:
VERSION: ${{ inputs.version }}
run: |
set -euo pipefail
work="$(mktemp -d)"
trap 'rm -rf "$work"' EXIT

install_with_retry() {
local package="$1"
local path="$2"
local attempt
for attempt in {1..12}; do
if dotnet tool install --tool-path "$path" "$package" --version "$VERSION" \
--add-source https://api.nuget.org/v3/index.json \
--ignore-failed-sources; then
return 0
fi
if [ "$attempt" -eq 12 ]; then
echo "::error::$package $VERSION was not consumable from NuGet.org after publication."
return 1
fi
sleep 10
done
}

install_with_retry Repo2C4.Cli "$work/cli"
install_with_retry Repo2C4.Mcp "$work/mcp"
install_with_retry Repo2C4.Agent "$work/agent"

"$work/cli/repo2c4" --help | grep -Fq 'Repo2C4 CLI'
if ! "$work/mcp/repo2c4-mcp" --help >"$work/mcp.stdout" 2>"$work/mcp.stderr"; then
cat "$work/mcp.stderr" >&2
exit 1
fi
test ! -s "$work/mcp.stdout"
grep -Fq 'stdout is reserved exclusively for MCP protocol messages' "$work/mcp.stderr"
"$work/agent/repo2c4-agent" --help >"$work/agent.stdout" 2>"$work/agent.stderr"
test ! -s "$work/agent.stdout"
grep -Fq 'Repo2C4 Agent host' "$work/agent.stderr"

echo "Published Repo2C4 CLI, MCP and Agent $VERSION installed and executed successfully from NuGet.org."


publish-mcp-registry:
name: Publish Repo2C4 MCP to Official Registry
needs: post-publish
if: ${{ needs.post-publish.result == 'success' }}
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- name: Checkout the exact released commit
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
ref: ${{ github.sha }}
persist-credentials: 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
test "$(jq -r '.name' server.json)" = "io.github.rodri-oliveira-dev/repo2c4-mcp"
test "$(jq -r '.version' server.json)" = "$VERSION"
test "$(jq -r '.packages | length' server.json)" -eq 1
test "$(jq -r '.packages[0].registryType' server.json)" = "nuget"
test "$(jq -r '.packages[0].identifier' server.json)" = "Repo2C4.Mcp"
test "$(jq -r '.packages[0].version' server.json)" = "$VERSION"
test "$(jq -r '.packages[0].runtimeHint' server.json)" = "dnx"
test "$(jq -r '.packages[0].transport.type' server.json)" = "stdio"
grep -Fq '<!-- mcp-name: io.github.rodri-oliveira-dev/repo2c4-mcp -->' 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."
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 \
"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"

"$publisher" validate server.json

status="$(curl --silent --show-error --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 server.json

for attempt in {1..12}; do
status="$(curl --silent --show-error --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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,7 @@ CLI help is written to stdout. MCP help and diagnostics are written **only to st

`.github/workflows/ci.yml` validates locked restore, formatting, Release build, tests, coverage, pinned LikeC4 integration, the complete offline CLI cycle (`inspect -> reviewed model -> generate -> validate`) and the full MCP protocol-client C1/C2 flow without paid AI or a proprietary client. CodeQL, Dependency Review and optional SonarQube Cloud checks remain available; [Sonar setup](docs/sonarqube-cloud.md) requires `SONAR_TOKEN`.

**Distribution:** the three versioned .NET tools, `Repo2C4.Cli`, `Repo2C4.Mcp` and `Repo2C4.Agent`, are clean-install tested. Public release remains manually gated; an authorized release publishes the validated packages to NuGet.org and GitHub Release, then verifies consumer installation. `Repo2C4.Mcp` also includes versioned Official MCP Registry metadata in [`server.json`](server.json); registry publication happens only after the matching NuGet package is public. `Repo2C4.Agent` remains a local .NET Tool/MCP client rather than a marketplace-specific agent package. See the [installation, security and release guide](docs/distribution.md) or [Português](docs/distribution.pt-BR.md).
**Distribution:** the three versioned .NET tools, `Repo2C4.Cli`, `Repo2C4.Mcp` and `Repo2C4.Agent`, are clean-install tested. Public release remains manually gated; an authorized release publishes the validated packages to NuGet.org and GitHub Release. NuGet indexing is asynchronous and is not used as a post-publication release gate. `Repo2C4.Mcp` also includes versioned Official MCP Registry metadata in [`server.json`](server.json); registry publication is a separate manual workflow run only after the matching NuGet package is publicly consumable. `Repo2C4.Agent` remains a local .NET Tool/MCP client rather than a marketplace-specific agent package. See the [installation, security and release guide](docs/distribution.md) or [Português](docs/distribution.pt-BR.md).



Expand Down
2 changes: 1 addition & 1 deletion README.pt-BR.md
Original file line number Diff line number Diff line change
Expand Up @@ -249,4 +249,4 @@ O [CI](.github/workflows/ci.yml) verifica restore locked, políticas de build, f

### Distribuição e automação

As ferramentas públicas são `Repo2C4.Cli` (comando `repo2c4`), `Repo2C4.Mcp` (comando `repo2c4-mcp`) e `Repo2C4.Agent` (comando `repo2c4-agent`). O LikeC4 continua uma dependência independente. A release pública é manual/protegida e publica os três pacotes no NuGet.org e GitHub Release. O `Repo2C4.Mcp` também possui metadata versionada para o Official MCP Registry em [`server.json`](server.json), publicada somente depois que a mesma versão estiver disponível no NuGet. O `Repo2C4.Agent` permanece uma .NET Tool local/cliente MCP, sem acoplamento a marketplace específico. Consulte [distribuição](docs/distribution.pt-BR.md) e [workflow de PR arquitetural](docs/architecture-pr.pt-BR.md).
As ferramentas públicas são `Repo2C4.Cli` (comando `repo2c4`), `Repo2C4.Mcp` (comando `repo2c4-mcp`) e `Repo2C4.Agent` (comando `repo2c4-agent`). O LikeC4 continua uma dependência independente. A release pública é manual/protegida e publica os três pacotes no NuGet.org e GitHub Release. A indexação do NuGet é assíncrona e não é usada como gate pós-publicação da release. O `Repo2C4.Mcp` também possui metadata versionada para o Official MCP Registry em [`server.json`](server.json), publicada por um workflow manual separado somente depois que a mesma versão estiver publicamente consumível no NuGet. O `Repo2C4.Agent` permanece uma .NET Tool local/cliente MCP, sem acoplamento a marketplace específico. Consulte [distribuição](docs/distribution.pt-BR.md) e [workflow de PR arquitetural](docs/architecture-pr.pt-BR.md).
Loading
Loading