Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
77 changes: 62 additions & 15 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,30 +1,52 @@
name: Release

# Triggered on every push to main. When package.json ships a version whose
# git tag doesn't exist yet, the workflow:
# 1. Runs the full CI gate.
# 2. Creates the `vX.Y.Z` tag automatically (no manual `git tag` needed).
# 3. Ships the .mcpb bundle to a GitHub Release AND publishes to npm
# (both jobs run in parallel — either can succeed while the other fails).
#
# When main advances without a version bump, the `detect` job flips
# should_release=false and every downstream job skips.
on:
push:
tags:
- 'v*.*.*'
branches: [main]

# Least-privilege default. Individual jobs opt into more when they need it.
permissions:
contents: read

jobs:
# ── Preflight: verify the tag was cut from main + run the full CI gate.
# Any tag whose commit is NOT reachable from origin/main is refused here so
# feature-branch tags can never trigger a release.
gate:
# ── Detect whether this push introduces a new version.
detect:
runs-on: ubuntu-latest
outputs:
version: ${{ steps.check.outputs.version }}
should_release: ${{ steps.check.outputs.should_release }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Verify tag commit is on main
- id: check
name: Read package.json version + check for existing tag
run: |
if ! git branch -r --contains "${GITHUB_SHA}" | grep -qE '(^|\s)origin/main$'; then
echo "::error::Tag ${GITHUB_REF_NAME} (${GITHUB_SHA}) is not reachable from origin/main. Releases can only be cut from main."
exit 1
VERSION=$(node -p "require('./package.json').version")
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
if git rev-parse "v$VERSION" >/dev/null 2>&1; then
echo "Tag v$VERSION already exists — skipping release."
echo "should_release=false" >> "$GITHUB_OUTPUT"
else
echo "New version v$VERSION — releasing."
echo "should_release=true" >> "$GITHUB_OUTPUT"
fi

# ── Full CI gate before anything ships.
gate:
runs-on: ubuntu-latest
needs: detect
if: needs.detect.outputs.should_release == 'true'
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '24'
Expand All @@ -37,11 +59,32 @@ jobs:
- run: npm test
- run: npm run build

# ── Create and push the vX.Y.Z tag.
tag:
runs-on: ubuntu-latest
needs: [detect, gate]
if: needs.detect.outputs.should_release == 'true'
permissions:
contents: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Create and push tag v${{ needs.detect.outputs.version }}
env:
VERSION: ${{ needs.detect.outputs.version }}
run: |
git config user.name 'github-actions[bot]'
git config user.email '41898282+github-actions[bot]@users.noreply.github.com'
git tag "v${VERSION}"
git push origin "v${VERSION}"

# ── Ship the Claude Desktop Extension bundle to the tag's GitHub Release.
# Independent of release-npm — either can succeed while the other fails.
release-mcpb:
runs-on: ubuntu-latest
needs: gate
needs: [detect, tag]
if: needs.detect.outputs.should_release == 'true'
permissions:
contents: write
steps:
Expand All @@ -58,20 +101,22 @@ jobs:
- name: Upload .mcpb + .zip to the tag's GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: v${{ needs.detect.outputs.version }}
files: |
dist/github-mcp-server-js-*.mcpb
dist/github-mcp-server-js-*.zip
fail_on_unmatched_files: true
generate_release_notes: true
prerelease: ${{ contains(github.ref_name, '-') }}
prerelease: ${{ contains(needs.detect.outputs.version, '-') }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

# ── Publish the npm package via Trusted Publisher (OIDC — no NPM_TOKEN).
# Independent of release-mcpb — either can succeed while the other fails.
release-npm:
runs-on: ubuntu-latest
needs: gate
needs: [detect, tag]
if: needs.detect.outputs.should_release == 'true'
environment: dev
permissions:
id-token: write
Expand All @@ -91,8 +136,10 @@ jobs:
# No NODE_AUTH_TOKEN — npm exchanges GitHub's OIDC token for a
# short-lived publish token via the trusted-publisher config on
# npmjs.com. See /package/github-mcp-server-js/access.
env:
VERSION: ${{ needs.detect.outputs.version }}
run: |
if [[ "${GITHUB_REF_NAME}" == *-* ]]; then
if [[ "$VERSION" == *-* ]]; then
npm publish --tag next --provenance --access public
else
npm publish --provenance --access public
Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ dist/
.env
.idea
dist/*.mcpb
dist/*.zip
dist/*.zip
.husky/
7 changes: 0 additions & 7 deletions .husky/pre-commit

This file was deleted.

4 changes: 1 addition & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -333,8 +333,6 @@ for public repositories).
| `rerun_workflow_run_failed_jobs` | W | Re-run only the failed jobs in a workflow run. |
| `approve_workflow_run` | W | Approve a workflow run awaiting fork-PR approval. |

See `docs/superpowers/specs/2026-08-05-github-mcp-server-design.md` for the full
architecture.

## 🧪 Testing

Expand Down Expand Up @@ -455,7 +453,7 @@ Every published version is built by GitHub Actions from a tagged commit, [signed
## 🙏 Credits

- **[octokit.js](https://github.com/octokit/octokit.js)** by GitHub — the REST/GraphQL client every tool wraps. Apache-2.0.
- **[MCP TypeScript SDK v2](https://github.com/modelcontextprotocol/typescript-sdk)** by Anthropic — the MCP server framework. MIT.
- **[MCP TypeScript](https://github.com/modelcontextprotocol/typescript-sdk)** by Anthropic — the MCP server framework. MIT.
- **Prior art:** [`@modelcontextprotocol/server-github`](https://github.com/modelcontextprotocol/servers-archived/tree/main/src/github) (archived, original Anthropic reference server) and [`github/github-mcp-server`](https://github.com/github/github-mcp-server) (GitHub's official Go / Docker implementation). Both remain excellent choices where their constraints fit.

## 📄 License
Expand Down
Loading
Loading