Skip to content

chore(release): cut v0.25.0 (#833) #62

chore(release): cut v0.25.0 (#833)

chore(release): cut v0.25.0 (#833) #62

Workflow file for this run

name: Release
on:
push:
tags:
- 'v*.*.*'
workflow_dispatch:
inputs:
ref:
description: 'Git ref (tag or branch) to release from'
required: true
default: 'refs/tags/v0.25.0'
permissions: {}
jobs:
build-and-test:
name: Build and Test
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
ref: ${{ github.event.inputs.ref || github.ref }}
- name: Setup pnpm
uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
# version comes from package.json "packageManager" (issue #478)
- name: Setup Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: '22.x'
cache: 'pnpm'
# Every publishing job needs this one, so a CHANGELOG section that is missing, or too long
# for a GitHub Release body without a Highlights block, stops the release here instead of
# in create-release after npm, Docker and PyPI have already published.
- name: Check release notes fit
env:
RELEASE_REF: ${{ github.event.inputs.ref || github.ref }}
run: node scripts/release-notes.mjs "$RELEASE_REF" --check
- name: Setup Python 3.11
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: '3.11'
- name: Install Python dependencies
run: |
python -m pip install --require-hashes -r requirements/pip.txt
python -m pip install --require-hashes -r requirements/debugpy.txt
- name: Setup Go
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version: '1.21'
- name: Install Delve debugger
run: go install github.com/go-delve/delve/cmd/dlv@v1.24.2
- name: Setup Java 21
uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1
with:
distribution: 'temurin'
java-version: '21'
- name: Setup Ruby
uses: ruby/setup-ruby@14594264cd68ce8a2345dd349bc3d138a4ef85c8 # v1.327.0
with:
ruby-version: '3.3'
- name: Verify rdbg (bundled debug gem)
run: rdbg --version
- name: Sanity versions
run: |
node -v
npm -v
pnpm -v
- name: Approve build scripts (esbuild)
run: pnpm approve-builds esbuild
continue-on-error: true
- name: Install dependencies
run: pnpm install --frozen-lockfile --ignore-scripts
- name: Audit production dependencies
run: pnpm audit --prod --audit-level=high
- name: Build project
run: pnpm run build
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Run tests
run: pnpm run test:ci-no-python
docker-publish:
name: Build and Push Docker Image
needs: build-and-test
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write # Required for signing the image attestation (OIDC)
attestations: write # Required to store the attestation
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
ref: ${{ github.event.inputs.ref || github.ref }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1
- name: Log in to Docker Hub
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Determine Docker latest tag strategy
shell: bash
env:
GITHUB_REF_TYPE: ${{ github.ref_type }}
GITHUB_REF_NAME: ${{ github.ref_name }}
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
run: |
if [[ "${GITHUB_REF_TYPE}" == "branch" && "${GITHUB_REF_NAME}" == "${DEFAULT_BRANCH}" ]]; then
echo "PUBLISH_LATEST=true" >> "$GITHUB_ENV"
echo "latest will track default branch build (${GITHUB_REF_NAME})"
elif [[ "${GITHUB_REF_TYPE}" == "tag" && "${GITHUB_REF_NAME}" != *"-alpha"* && "${GITHUB_REF_NAME}" != *"-beta"* && "${GITHUB_REF_NAME}" != *"-rc"* ]]; then
echo "PUBLISH_LATEST=true" >> "$GITHUB_ENV"
echo "latest will point to release ${GITHUB_REF_NAME}"
else
echo "PUBLISH_LATEST=false" >> "$GITHUB_ENV"
echo "latest tag update skipped for ref ${GITHUB_REF_NAME}"
fi
- name: Extract metadata
id: meta
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0
with:
images: debugmcp/mcp-debugger
tags: |
type=ref,event=tag
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=raw,value=latest,enable=${{ env.PUBLISH_LATEST == 'true' }}
- name: Build and push Docker image
id: build
uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
# Attest the pushed image digest so the Docker artifact is verifiable back
# to this workflow and commit, like the npm tarballs already are:
# gh attestation verify oci://index.docker.io/debugmcp/mcp-debugger:<tag> \
# --repo debugmcp/mcp-debugger
# Same composite-action approach as the provenance job below (see the note
# there for why slsa-github-generator's reusable workflows are not usable
# in this org).
- name: Attest Docker image provenance
uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2
with:
subject-name: index.docker.io/debugmcp/mcp-debugger
subject-digest: ${{ steps.build.outputs.digest }}
push-to-registry: true
pypi-publish:
name: Publish Python Launcher to PyPI
needs: build-and-test
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write # Required for PyPI trusted publishing (OIDC)
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
ref: ${{ github.event.inputs.ref || github.ref }}
- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: '3.11'
- name: Install Python build dependencies
run: |
python -m pip install "pip==25.0.1"
pip install "build==1.2.2" "twine==6.1.0" "tomlkit==0.13.2"
- name: Resolve release ref
run: |
if [ -n "${{ github.event.inputs.ref }}" ]; then
echo "RELEASE_REF=${{ github.event.inputs.ref }}" >> $GITHUB_ENV
else
echo "RELEASE_REF=${GITHUB_REF}" >> $GITHUB_ENV
fi
- name: Set launcher version from tag
run: |
VERSION="${RELEASE_REF#refs/tags/v}"
echo "VERSION=$VERSION" >> $GITHUB_ENV
- name: Sync version into pyproject.toml
run: |
python - <<'PY'
import os
from tomlkit import parse, dumps
p = os.path.join('mcp_debugger_launcher','pyproject.toml')
with open(p,'r',encoding='utf-8') as f:
doc = parse(f.read())
ver = os.environ.get('VERSION','0.0.0')
if 'project' in doc and 'version' in doc['project']:
doc['project']['version'] = ver
elif 'tool' in doc and 'poetry' in doc['tool'] and 'version' in doc['tool']['poetry']:
doc['tool']['poetry']['version'] = ver
else:
doc.setdefault('project', {})['version'] = ver
with open(p,'w',encoding='utf-8') as f:
f.write(dumps(doc))
print(f"Set pyproject version to {ver}")
PY
- name: Build Python package
run: |
cd mcp_debugger_launcher
rm -rf dist build *.egg-info
python -m build
- name: Check package metadata
run: |
cd mcp_debugger_launcher
python -m twine check dist/*
# PyPI trusted publishing (OIDC) - no long-lived token. The action also
# generates and uploads PEP 740 digital attestations for the dists.
# Requires the trusted publisher configured on pypi.org for
# debug-mcp-server-launcher (repo debugmcp/mcp-debugger, workflow
# release.yml).
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
with:
packages-dir: mcp_debugger_launcher/dist
skip-existing: true
npm-publish:
name: Publish to npm
needs: build-and-test
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write # Required for npm trusted publishing (OIDC)
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
ref: ${{ github.event.inputs.ref || github.ref }}
- name: Setup pnpm
uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
# version comes from package.json "packageManager" (issue #478)
# No registry-url here: setup-node would write an _authToken=${NODE_AUTH_TOKEN}
# placeholder into .npmrc, which breaks token-less OIDC publishes when the
# env var is unset. The token step below writes its own scoped .npmrc.
- name: Setup Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: '22.x'
cache: 'pnpm'
# npm >= 11.5.1 is required for OIDC trusted publishing; Node 22 bundles npm 10.
- name: Upgrade npm for trusted publishing
run: npm install -g npm@12.0.2
- name: Setup Java 21
uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1
with:
distribution: 'temurin'
java-version: '21'
- name: Setup Ruby
uses: ruby/setup-ruby@14594264cd68ce8a2345dd349bc3d138a4ef85c8 # v1.327.0
with:
ruby-version: '3.3'
- name: Install dependencies
run: pnpm install --frozen-lockfile --ignore-scripts
- name: Sanity versions
run: |
node -v
npm -v
pnpm -v
- name: Resolve release ref
run: |
if [ -n "${{ github.event.inputs.ref }}" ]; then
echo "RELEASE_REF=${{ github.event.inputs.ref }}" >> $GITHUB_ENV
else
echo "RELEASE_REF=${GITHUB_REF}" >> $GITHUB_ENV
fi
- name: Set CLI package version from tag (monorepo-safe)
run: |
VERSION="${RELEASE_REF#refs/tags/v}"
echo "Setting CLI package version to $VERSION"
VERSION_STRIPPED="$VERSION" node -e "const fs=require('fs');const p='packages/mcp-debugger/package.json';const pkg=JSON.parse(fs.readFileSync(p,'utf8'));const ver=process.env.VERSION_STRIPPED; if(!/^[0-9]+\\.[0-9]+\\.[0-9]+(-.+)?$/.test(ver)){console.error('Invalid semver:',ver);process.exit(1);} pkg.version=ver; fs.writeFileSync(p,JSON.stringify(pkg,null,2)+'\\n');console.log('Updated',p,'to',pkg.version)"
- name: Capture workspace package versions (robust)
run: |
node -e 'console.log("SHARED_VERSION="+require("./packages/shared/package.json").version)' >> $GITHUB_ENV
node -e 'console.log("ADAPTER_MOCK_VERSION="+require("./packages/adapter-mock/package.json").version)' >> $GITHUB_ENV
node -e 'console.log("ADAPTER_PYTHON_VERSION="+require("./packages/adapter-python/package.json").version)' >> $GITHUB_ENV
node -e 'console.log("ADAPTER_RUBY_VERSION="+require("./packages/adapter-ruby/package.json").version)' >> $GITHUB_ENV
node -e 'console.log("ADAPTER_JAVASCRIPT_VERSION="+require("./packages/adapter-javascript/package.json").version)' >> $GITHUB_ENV
node -e 'console.log("ADAPTER_GO_VERSION="+require("./packages/adapter-go/package.json").version)' >> $GITHUB_ENV
node -e 'console.log("ADAPTER_JAVA_VERSION="+require("./packages/adapter-java/package.json").version)' >> $GITHUB_ENV
node -e 'console.log("ADAPTER_DOTNET_VERSION="+require("./packages/adapter-dotnet/package.json").version)' >> $GITHUB_ENV
node -e 'console.log("CLI_VERSION="+require("./packages/mcp-debugger/package.json").version)' >> $GITHUB_ENV
# CodeLLDB platform packages are versioned by the vendored CodeLLDB release (issue #383)
node -e 'console.log("CODELLDB_PKG_VERSION="+require("./packages/codelldb-common/vendor-manifest.json").codelldb.version)' >> $GITHUB_ENV
- name: Set npm dist-tag
run: |
if [[ "${CLI_VERSION}" == *"-beta"* ]] || [[ "${CLI_VERSION}" == *"-alpha"* ]]; then
echo "NPM_TAG=beta" >> $GITHUB_ENV
else
echo "NPM_TAG=latest" >> $GITHUB_ENV
fi
- name: Build project
run: pnpm run build
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Downloads the digest-pinned VSIXs for all five platforms (the build above
# vendored linux-x64 only under CI=true) and copies the payloads into the
# @debugmcp/codelldb-* platform packages. Verification runs in a SEPARATE
# process: a vendoring process that died silently (issue #389) cannot
# vouch for itself, and --verify-only fails the job if any package would
# pack without its binaries (issue #383).
- name: Stage CodeLLDB platform packages
run: |
node scripts/stage-codelldb-packages.mjs
node scripts/stage-codelldb-packages.mjs --verify-only
# npm (unlike pnpm) does not rewrite workspace:* at publish time; without
# this step the published adapter packages carry an uninstallable
# "workspace:*" dependency on @debugmcp/shared (EUNSUPPORTEDPROTOCOL).
# The rewrite happens only in the runner checkout, never committed.
- name: Resolve workspace deps for publish
run: node scripts/resolve-workspace-deps.cjs
- name: Pack npm tarballs (dry-run)
run: |
npm pack --dry-run -w @debugmcp/shared
npm pack --dry-run -w @debugmcp/adapter-mock
npm pack --dry-run -w @debugmcp/adapter-python
npm pack --dry-run -w @debugmcp/adapter-ruby
npm pack --dry-run -w @debugmcp/adapter-javascript
npm pack --dry-run -w @debugmcp/adapter-go
npm pack --dry-run -w @debugmcp/adapter-java
npm pack --dry-run -w @debugmcp/adapter-dotnet
npm pack --dry-run -w @debugmcp/mcp-debugger
npm pack --dry-run -w @debugmcp/codelldb-win32-x64
npm pack --dry-run -w @debugmcp/codelldb-darwin-x64
npm pack --dry-run -w @debugmcp/codelldb-darwin-arm64
npm pack --dry-run -w @debugmcp/codelldb-linux-x64
npm pack --dry-run -w @debugmcp/codelldb-linux-arm64
# CodeLLDB platform packages (issue #383). Published BEFORE the CLI package
# so its optionalDependencies always resolve on the registry. Versioned by
# the CodeLLDB release and tagged latest explicitly (they are shared across
# beta/latest channels), so re-releases at an unchanged pin are skipped by
# the npm-view guards. First publish must use the token flow; once the five
# exist, configure trusted publishers and move them into the OIDC step.
- name: Publish CodeLLDB platform packages to npm (token, first publish)
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: |
# trap (not a trailing rm) so the token file cannot survive a
# mid-loop publish failure on the runner.
trap 'rm -f "$HOME/.npmrc"' EXIT
echo "//registry.npmjs.org/:_authToken=\${NODE_AUTH_TOKEN}" > "$HOME/.npmrc"
# "already exists" is the NORMAL path here (every re-release at an
# unchanged CodeLLDB pin walks it), so the guard must distinguish a
# true 404 from a transient registry error - publishing over an
# existing version would otherwise kill the job before the OIDC step.
for pkg in codelldb-win32-x64 codelldb-darwin-x64 codelldb-darwin-arm64 codelldb-linux-x64 codelldb-linux-arm64; do
set +e
VIEW_OUT=$(npm view @debugmcp/${pkg}@${CODELLDB_PKG_VERSION} version 2>&1)
VIEW_CODE=$?
set -e
if [ "$VIEW_CODE" -eq 0 ]; then
echo "@debugmcp/${pkg}@${CODELLDB_PKG_VERSION} already exists, skipping"
elif echo "$VIEW_OUT" | grep -q "E404"; then
npm publish -w @debugmcp/${pkg} --access public --provenance --tag latest
else
echo "npm view for @debugmcp/${pkg}@${CODELLDB_PKG_VERSION} failed with a non-404 error; refusing to guess:"
echo "$VIEW_OUT"
exit 1
fi
done
# Existing packages publish via OIDC trusted publishing: no token anywhere,
# npm mints a short-lived credential from the workflow's OIDC identity and
# generates provenance automatically. Each package must have this repo +
# workflow configured as a trusted publisher on npmjs.com BEFORE tagging.
- name: Publish existing packages to npm (OIDC trusted publishing)
run: |
# Publish packages in dependency order if the target version does NOT yet exist
if npm view @debugmcp/shared@${SHARED_VERSION} version >/dev/null 2>&1; then
echo "@debugmcp/shared@${SHARED_VERSION} already exists, skipping"
else
npm publish -w @debugmcp/shared --access public --provenance --tag ${NPM_TAG}
fi
if npm view @debugmcp/adapter-mock@${ADAPTER_MOCK_VERSION} version >/dev/null 2>&1; then
echo "@debugmcp/adapter-mock@${ADAPTER_MOCK_VERSION} already exists, skipping"
else
npm publish -w @debugmcp/adapter-mock --access public --provenance --tag ${NPM_TAG}
fi
if npm view @debugmcp/adapter-python@${ADAPTER_PYTHON_VERSION} version >/dev/null 2>&1; then
echo "@debugmcp/adapter-python@${ADAPTER_PYTHON_VERSION} already exists, skipping"
else
npm publish -w @debugmcp/adapter-python --access public --provenance --tag ${NPM_TAG}
fi
if npm view @debugmcp/adapter-ruby@${ADAPTER_RUBY_VERSION} version >/dev/null 2>&1; then
echo "@debugmcp/adapter-ruby@${ADAPTER_RUBY_VERSION} already exists, skipping"
else
npm publish -w @debugmcp/adapter-ruby --access public --provenance --tag ${NPM_TAG}
fi
if npm view @debugmcp/mcp-debugger@${CLI_VERSION} version >/dev/null 2>&1; then
echo "@debugmcp/mcp-debugger@${CLI_VERSION} already exists, skipping"
else
npm publish -w @debugmcp/mcp-debugger --access public --provenance --tag ${NPM_TAG}
fi
# First-time publishes cannot use trusted publishing (the trusted-publisher
# config lives on an existing npm package). These four publish with the
# scoped NPM_TOKEN this release only; once they exist, configure trusted
# publishers for them and move them into the OIDC step above.
- name: Publish new packages to npm (token, first publish)
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: |
echo "//registry.npmjs.org/:_authToken=\${NODE_AUTH_TOKEN}" > "$HOME/.npmrc"
if npm view @debugmcp/adapter-javascript@${ADAPTER_JAVASCRIPT_VERSION} version >/dev/null 2>&1; then
echo "@debugmcp/adapter-javascript@${ADAPTER_JAVASCRIPT_VERSION} already exists, skipping"
else
npm publish -w @debugmcp/adapter-javascript --access public --provenance --tag ${NPM_TAG}
fi
if npm view @debugmcp/adapter-go@${ADAPTER_GO_VERSION} version >/dev/null 2>&1; then
echo "@debugmcp/adapter-go@${ADAPTER_GO_VERSION} already exists, skipping"
else
npm publish -w @debugmcp/adapter-go --access public --provenance --tag ${NPM_TAG}
fi
if npm view @debugmcp/adapter-java@${ADAPTER_JAVA_VERSION} version >/dev/null 2>&1; then
echo "@debugmcp/adapter-java@${ADAPTER_JAVA_VERSION} already exists, skipping"
else
npm publish -w @debugmcp/adapter-java --access public --provenance --tag ${NPM_TAG}
fi
if npm view @debugmcp/adapter-dotnet@${ADAPTER_DOTNET_VERSION} version >/dev/null 2>&1; then
echo "@debugmcp/adapter-dotnet@${ADAPTER_DOTNET_VERSION} already exists, skipping"
else
npm publish -w @debugmcp/adapter-dotnet --access public --provenance --tag ${NPM_TAG}
fi
rm -f "$HOME/.npmrc"
# The packed tarballs become GitHub Release assets, attested by the
# provenance job below (OpenSSF Scorecard Signed-Releases).
- name: Pack release artifacts for provenance
run: |
mkdir -p release-artifacts
npm pack -w @debugmcp/shared --pack-destination release-artifacts
npm pack -w @debugmcp/adapter-mock --pack-destination release-artifacts
npm pack -w @debugmcp/adapter-python --pack-destination release-artifacts
npm pack -w @debugmcp/adapter-ruby --pack-destination release-artifacts
npm pack -w @debugmcp/adapter-javascript --pack-destination release-artifacts
npm pack -w @debugmcp/adapter-go --pack-destination release-artifacts
npm pack -w @debugmcp/adapter-java --pack-destination release-artifacts
npm pack -w @debugmcp/adapter-dotnet --pack-destination release-artifacts
npm pack -w @debugmcp/mcp-debugger --pack-destination release-artifacts
npm pack -w @debugmcp/codelldb-win32-x64 --pack-destination release-artifacts
npm pack -w @debugmcp/codelldb-darwin-x64 --pack-destination release-artifacts
npm pack -w @debugmcp/codelldb-darwin-arm64 --pack-destination release-artifacts
npm pack -w @debugmcp/codelldb-linux-x64 --pack-destination release-artifacts
npm pack -w @debugmcp/codelldb-linux-arm64 --pack-destination release-artifacts
- name: Upload release artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: release-artifacts
path: release-artifacts/*.tgz
if-no-files-found: error
provenance:
name: Generate build provenance attestation
needs: npm-publish
runs-on: ubuntu-latest
permissions:
id-token: write # sign the attestation (sigstore)
attestations: write # persist it to the GitHub Attestations API
contents: read
# Uses actions/attest-build-provenance (a plain composite action) rather than
# slsa-framework/slsa-github-generator's reusable workflow: this org has
# "Write permissions for workflows" disabled, and GitHub validates a calling
# job's permissions against that policy at PARSE TIME for external reusable
# *workflow* calls specifically -- even read-only-looking permission sets on
# such a job made the whole run fail with startup_failure before any job
# (even unrelated ones) could start. A normal action inside a normal job
# (like npm-publish's existing id-token: write) isn't subject to that check.
steps:
- name: Download release artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: release-artifacts
path: release-artifacts
- name: Generate attestation
id: attest
uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2
with:
subject-path: release-artifacts/*.tgz
# The bundle is a JSON-serialized Sigstore bundle wrapping an in-toto
# statement -- genuinely valid under both extensions Scorecard's
# Signed-Releases probes scan release assets for (releasesAreSigned:
# .sigstore.json; releasesHaveProvenance: .intoto.jsonl).
- name: Name provenance files for release assets
run: |
cp "${{ steps.attest.outputs.bundle-path }}" multiple.intoto.jsonl
cp "${{ steps.attest.outputs.bundle-path }}" multiple.sigstore.json
- name: Upload provenance files
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: provenance
path: |
multiple.intoto.jsonl
multiple.sigstore.json
if-no-files-found: error
create-release:
name: Create GitHub Release
needs: [docker-publish, pypi-publish, npm-publish, provenance]
runs-on: ubuntu-latest
permissions:
contents: write # Grant permission to create releases
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
ref: ${{ github.event.inputs.ref || github.ref }}
# Pinned rather than relying on the runner image's Node: this job runs after every publish,
# so it must not depend on anything that can drift underneath it.
- name: Setup Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: '22.x'
- name: Resolve release ref
run: |
if [ -n "${{ github.event.inputs.ref }}" ]; then
echo "RELEASE_REF=${{ github.event.inputs.ref }}" >> $GITHUB_ENV
else
echo "RELEASE_REF=${GITHUB_REF}" >> $GITHUB_ENV
fi
- name: Download release artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: release-artifacts
path: release-artifacts
- name: Download provenance attestation
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: provenance
path: provenance
# The CHANGELOG section goes to a file, not a step output: interpolating a ~100 KB output into
# the release script is fragile, and a section over GitHub's release-body limit is replaced by
# its Highlights block and a link (scripts/release-notes.mjs; build-and-test already checked it).
- name: Generate changelog
id: changelog
run: |
# Extract version from tag
VERSION=${RELEASE_REF#refs/tags/v}
echo "VERSION=$VERSION" >> $GITHUB_OUTPUT
node scripts/release-notes.mjs "$VERSION" --out release-changelog.md
# SBOMs (SPDX + CycloneDX) are generated from the exact release ref and
# attached to the GitHub release — required by many corporate/government
# procurement processes (EO 14028) and useful for downstream scanners.
- name: Generate SBOM (SPDX)
uses: anchore/sbom-action@3ad7283483fc7af8ff2b4ea19663c2d5ca935e26 # v0.24.2
with:
path: .
format: spdx-json
output-file: sbom.spdx.json
upload-artifact: false
upload-release-assets: false
- name: Generate SBOM (CycloneDX)
uses: anchore/sbom-action@3ad7283483fc7af8ff2b4ea19663c2d5ca935e26 # v0.24.2
with:
path: .
format: cyclonedx-json
output-file: sbom.cyclonedx.json
upload-artifact: false
upload-release-assets: false
- name: Create Release with GitHub CLI
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# Create the release body in a file: heading, the changelog body chosen by
# scripts/release-notes.mjs, then the fixed installation and verification text.
{
printf '## 🎉 Release %s\n\n' "${{ steps.changelog.outputs.VERSION }}"
cat release-changelog.md
printf '\n'
} > release_notes.md
cat << 'EOF' >> release_notes.md
### 📦 Installation
**Docker:**
```bash
docker pull debugmcp/mcp-debugger:${{ steps.changelog.outputs.VERSION }}
```
**npm (global install):**
```bash
npm install -g @debugmcp/mcp-debugger@${{ steps.changelog.outputs.VERSION }}
```
**npx (no install):**
```bash
npx @debugmcp/mcp-debugger@${{ steps.changelog.outputs.VERSION }} stdio
```
**PyPI:**
```bash
pip install debug-mcp-server-launcher==${{ steps.changelog.outputs.VERSION }}
```
**Optional adapters** (for programmatic embedding; the CLI above bundles all of them):
```bash
npm install @debugmcp/adapter-python # or: adapter-ruby, adapter-javascript,
# adapter-go, adapter-java, adapter-dotnet,
# adapter-mock
```
### 🔏 Verify this release
```bash
gh attestation verify <asset>.tgz --repo debugmcp/mcp-debugger
gh attestation verify oci://index.docker.io/debugmcp/mcp-debugger:${{ steps.changelog.outputs.VERSION }} --repo debugmcp/mcp-debugger
npm audit signatures # in a project that installs @debugmcp packages
```
The PyPI launcher carries PEP 740 attestations — see the "Verified details" on its [PyPI page](https://pypi.org/project/debug-mcp-server-launcher/${{ steps.changelog.outputs.VERSION }}/).
SBOMs (SPDX + CycloneDX) are attached as release assets.
### 📚 Documentation
See the [README](https://github.com/debugmcp/mcp-debugger#readme) for usage instructions.
EOF
# Determine if this is a prerelease
if [[ "${{ github.ref_name }}" == *"-beta"* ]] || [[ "${{ github.ref_name }}" == *"-alpha"* ]]; then
PRERELEASE_FLAG="--prerelease"
else
PRERELEASE_FLAG=""
fi
# Create the release using GitHub CLI, attaching the npm tarballs and
# their build provenance attestation (verify with:
# gh attestation verify <tarball> --repo debugmcp/mcp-debugger)
gh release create "${{ github.ref_name }}" \
--title "Release ${{ steps.changelog.outputs.VERSION }}" \
--notes-file release_notes.md \
$PRERELEASE_FLAG \
release-artifacts/*.tgz \
provenance/multiple.intoto.jsonl \
provenance/multiple.sigstore.json \
sbom.spdx.json \
sbom.cyclonedx.json