Skip to content

Commit c8defd6

Browse files
authored
fix(scaffold): mcp-server release is PAT-free and protected-main-safe [skip version] (#78)
The mcp-server release template pushed a version-bump commit to main, which the standard main-protection ruleset (empty bypass) rejects with 403, so a born repo could never release/publish. Replace with the validated tag-only model: bump the version in the PR; release.yml uses the default GITHUB_TOKEN to push only tags (never main) and create the release, then dispatches publish.yml (a GITHUB_TOKEN release does not trigger it; publish.yml is idempotent). No dependency on a personal access token. AGENTS.md.j2 and CLAUDE.md.j2 mcp-server version guidance updated to match (bump in the PR; CI never writes to main). No STANDARDS_VERSION/VERSION change. Signed-off-by: fOuttaMyPaint <tmhospitalitystrategies@gmail.com>
1 parent 99a1962 commit c8defd6

3 files changed

Lines changed: 25 additions & 62 deletions

File tree

scaffold/templates/AGENTS.md.j2

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ Builds and runs the test suite on Node 20 and 22:
7878

7979
### `release.yml` (runs on push to main)
8080

81-
Conventional-commit auto-bump: determines the bump type from commit messages since the last tag, updates `package.json`, creates a git tag and GitHub Release.
81+
Reads the version from `package.json` and, if there is no matching tag yet, pushes the `v<version>` tag (plus the floating `vMAJOR` and `vMAJOR.MINOR` tags), creates a GitHub Release, and dispatches `publish.yml`. It only pushes tags and never writes to `main`; bump the version in your PR.
8282

8383
### `publish.yml` (runs on release published or workflow_dispatch)
8484

@@ -105,11 +105,13 @@ Keeps repository labels in sync.
105105

106106
{% if type == 'cursor-plugin' %}
107107
- The **source of truth** for the current version is `.cursor-plugin/plugin.json`.
108+
- The release workflow auto-bumps it and the README badge on every qualifying push to main.
109+
- Never manually change the version.
108110
{% else %}
109111
- The **source of truth** for the current version is `package.json`.
112+
- Bump it in your PR with `npm version <patch|minor|major> --no-git-tag-version` (keeps the lockfile in sync) and update the README badge, following conventional-commit intent.
113+
- On merge, `release.yml` tags that version and publishes it. `main` is protected and is never written to by CI.
110114
{% endif %}
111-
- The release workflow auto-bumps it and the README badge on every qualifying push to main.
112-
- Never manually change the version.
113115

114116
## Code conventions
115117

scaffold/templates/CLAUDE.md.j2

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ This file provides guidance for Claude Code when working in this repository.
4141
- All rules need frontmatter with description, globs, alwaysApply
4242
{% else %}
4343
- Use conventional commits (`feat:`, `fix:`, `chore:`, `docs:`)
44-
- Never manually edit the version in `package.json` -- CI auto-bumps it
44+
- Bump the version in `package.json` in your PR (`npm version`, keeps the lockfile in sync); `release.yml` tags and publishes that version on merge
4545
- Provider adapters live in `src/providers/` and implement the `Provider` interface, wired into `ProviderManager`; tools live in `src/tools/`
4646
- Keep `mcp-tools.json` in sync with the registered tools
4747
{% endif %}

scaffold/templates/release.mcp.yml.j2

Lines changed: 19 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -7,99 +7,60 @@ on:
77

88
permissions:
99
contents: write
10+
actions: write
1011

1112
concurrency:
1213
group: release
1314
cancel-in-progress: false
1415

1516
jobs:
16-
release:
17-
name: Version, tag, and release
17+
tag-and-release:
18+
name: Tag and release
1819
runs-on: ubuntu-latest
19-
# Skip the release commit's own push (it carries [skip version]) so the
20-
# job never re-triggers itself into a loop.
21-
if: "!contains(github.event.head_commit.message, '[skip version]')"
2220
steps:
21+
# The default GITHUB_TOKEN (contents: write) pushes tags and creates the
22+
# release. main is protected and is NEVER written to by CI; bump the
23+
# version in your PR. A GITHUB_TOKEN release does not trigger publish.yml,
24+
# so this job dispatches it explicitly afterward.
2325
- uses: actions/checkout@v6
2426
with:
2527
fetch-depth: 0
26-
# A PAT lets the release event trigger publish.yml. GITHUB_TOKEN
27-
# pushes/releases do NOT trigger downstream workflows by design,
28-
# which is why publish never fires without this. Falls back to
29-
# GITHUB_TOKEN (publish.yml can then be run via workflow_dispatch).
30-
token: ${{ secrets.RELEASE_PAT != '' && secrets.RELEASE_PAT || secrets.GITHUB_TOKEN }}
3128

32-
- uses: actions/setup-node@v6
33-
with:
34-
node-version: 22
35-
36-
- name: Determine bump from conventional commits
37-
id: bump
38-
run: |
39-
set -euo pipefail
40-
last_tag=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
41-
if [ -z "$last_tag" ]; then
42-
range_log=$(git log --pretty=format:'%s%n%b')
43-
else
44-
range_log=$(git log "${last_tag}..HEAD" --pretty=format:'%s%n%b')
45-
fi
46-
bump=patch
47-
if printf '%s\n' "$range_log" | grep -qE 'BREAKING CHANGE' \
48-
|| printf '%s\n' "$range_log" | grep -qE '^[a-z]+(\([^)]*\))?!:'; then
49-
bump=major
50-
elif printf '%s\n' "$range_log" | grep -qE '^feat(\([^)]*\))?:'; then
51-
bump=minor
52-
fi
53-
echo "bump=$bump" >> "$GITHUB_OUTPUT"
54-
echo "Computed bump: $bump (since ${last_tag:-<initial>})"
55-
56-
- name: Apply version bump
29+
- name: Read version from package.json
5730
id: ver
5831
run: |
59-
set -euo pipefail
60-
new=$(npm version "${{ steps.bump.outputs.bump }}" --no-git-tag-version)
61-
new=${new#v}
62-
echo "version=$new" >> "$GITHUB_OUTPUT"
63-
echo "New version: $new"
64-
65-
- name: Update README version badge
66-
run: |
67-
set -euo pipefail
68-
v="${{ steps.ver.outputs.version }}"
69-
if [ -f README.md ]; then
70-
sed -i -E "s|(badge/version-)[0-9]+\.[0-9]+\.[0-9]+(-blue)|\1${v}\2|g" README.md || true
71-
fi
32+
v=$(node -p "require('./package.json').version")
33+
echo "version=$v" >> "$GITHUB_OUTPUT"
34+
echo "package.json version: $v"
7235

73-
- name: Guard against re-tagging an existing version
36+
- name: Check if tag already exists
7437
id: check
7538
run: |
76-
set -euo pipefail
77-
v="${{ steps.ver.outputs.version }}"
78-
if git rev-parse "v$v" >/dev/null 2>&1; then
39+
if git rev-parse "v${{ steps.ver.outputs.version }}" >/dev/null 2>&1; then
7940
echo "skip=true" >> "$GITHUB_OUTPUT"
80-
echo "Tag v$v already exists; skipping release."
41+
echo "Tag v${{ steps.ver.outputs.version }} exists; nothing to release."
8142
else
8243
echo "skip=false" >> "$GITHUB_OUTPUT"
8344
fi
8445

85-
- name: Commit, tag, and create release
46+
- name: Tag, release, and trigger publish
8647
if: steps.check.outputs.skip == 'false'
8748
env:
88-
GH_TOKEN: ${{ secrets.RELEASE_PAT != '' && secrets.RELEASE_PAT || secrets.GITHUB_TOKEN }}
49+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
8950
run: |
9051
set -euo pipefail
9152
v="${{ steps.ver.outputs.version }}"
9253
IFS='.' read -r major minor _patch <<< "$v"
9354
git config user.name "github-actions[bot]"
9455
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
95-
git add package.json package-lock.json README.md
96-
git commit -m "chore(release): v$v [skip version]"
9756
git tag "v$v"
9857
git tag -f "v$major"
9958
git tag -f "v$major.$minor"
100-
git push origin HEAD:main
10159
git push origin "v$v"
10260
git push origin "v$major" --force
10361
git push origin "v$major.$minor" --force
10462
gh release create "v$v" --title "v$v" --generate-notes
63+
# publish.yml is idempotent (skips an already-published version), so a
64+
# duplicate dispatch is harmless.
65+
gh workflow run publish.yml --ref main
10566
{% endraw %}

0 commit comments

Comments
 (0)