Skip to main content

release

Cut a milestone release: the release issue, the preparation PRs on v2/main (audit, changesets Version Packages, Python CalVer), the pure v2/main to main merge PR with its release ledger, then the GitHub Release a maintainer publishes.

ソース情報

リポジトリ
modelcontextprotocol/servers
ソースの最終更新活動
2026年10月27日 02:46
検出された SKILL.md の言語
英語
スター
91,652
フォーク
11,880

インストール方法

デフォルトでは、最初にソースを確認する Prompt が選択されています。直接コマンドに切り替えるか、ローカルコピーをダウンロードすることもできます。

ソースファイルを確認

インストールを決める前に、SKILL.md と SkillsMP に表示されている付属ファイルをお読みください。

SKILL.md を表示中

SKILL.md
ソースの指示 · 読み取り専用プレビュー
name
release
description
Cut a milestone release: the release issue, the preparation PRs on v2/main (audit, changesets Version Packages, Python CalVer), the pure v2/main to main merge PR with its release ledger, then the GitHub Release a maintainer publishes.
disable-model-invocation
true
# Cutting a milestone release How versions and publishing work, and what to do when a publish fails, is [`RELEASING.md`](../../../RELEASING.md). This skill is the procedure for one milestone, start to finish. It is started by name only (`/release`), because a release is never something to begin by inference. A release is cut from **`main`**, after the milestone's work has been merged there from `v2/main`. Nothing publishes from `v2/main`: `release.yml` refuses a Release whose commit is not on `main`. **What stays human.** Publishing the GitHub Release and approving the `release` environment deployments are a maintainer's acts. So is merging any PR. Prepare, verify and recommend; do not perform those. ## 0. Check this session's instructions first A release is the longest-running session and the costliest to get wrong, and it follows this skill and `AGENTS.md` exactly as they were when the session started. Before step 1, run the staleness check in `AGENTS.md` **Issue-driven work style** (all work in a worktree freshly cut from `origin/v2/main`), whatever the `SessionStart` hook printed: the hook ran when the session began, and this check runs now. On `STALE`, or a `STALE` line from the `SessionStart` hook, **stop** and ask for a session started in a fresh worktree. The v2.0.0 release ran its first three steps on a `release` skill 447 commits out of date (#5167). Re-run the check after each preparation PR merges, since `v2/main` moves under the session, **from the session-start revision, not `HEAD`**: this flow moves between worktrees (step 4e moves the verification worktree onto a newer candidate), and a newer worktree's `HEAD` says nothing about the rules this session loaded. The hook prints that revision and the re-check command. ## The shape One **release issue**, up to three **preparation PRs** on `v2/main`, one **merge PR** into `main`, then the **Release**. In that order: each step needs the one before it merged. | | Preparation PRs | The merge PR | | --- | --- | --- | | Base | **`v2/main`** | **`main`** | | Carries | The audit's forced fixes; the TypeScript version bumps; the Python version stamps | The milestone's work, arriving whole from `v2/main`. **No commits of its own** | | References the release issue with | `Closes #N` (a cross-reference only, on `v2/main`) | `Part of #N` (**non-closing**: `main` is the default branch, where `Closes` would close the issue before anything is published) | | Verified by | `npm run local:gate`, CI | `npm run local:gate`, **plus** `npm run pack:verify`, **plus** every milestone issue exercised, written up as the **release ledger** | ⚠️ **Do not fold them together.** A version bump made on the merge branch exists only downstream of `v2/main`, and nothing carries it back: the develop branch would keep reporting the previous version, and the next branch cut from it would too. ## 1. The release issue One per milestone, titled **`Release vX.Y.Z`** (the milestone's name), filed through `/issue-create`: type `chore`, no server-scope label, the milestone it releases. It is what every PR below references, so file it first. Set its card to **In Progress** (`/board-ops`). It stays open until the Release is published (step 6), whatever merges in between. ```sh N=<RELEASE_ISSUE_NUMBER> MILESTONE=vX.Y.Z ``` **Check the milestone is complete** before preparing anything. The only open issue left in it should be the release issue itself: ```sh gh issue list --repo modelcontextprotocol/servers --milestone "$MILESTONE" \ --state open --json number,title --jq '.[] | "#\(.number) \(.title)"' ``` Anything else open is either finished first or moved to the next milestone by a maintainer. Do not release around it silently. ## 2. Preparation PRs, on `v2/main` All three are part of the milestone's work, so all three land on the develop branch and reach `main` with everything else. **All are merged before the merge PR is opened.** A milestone that touches only one language, with a clean audit, has one. Each branch this skill cuts by hand (the audit, the hand-run CalVer stamp, the merge branch) goes into **its own sibling worktree**, as `/pr-flow` step 2 does, never by switching branches in the primary checkout. Run each recipe from the primary checkout; the worktree is named for the checkout, the release issue and the branch's role, since one issue owns several branches. ### 2a. The audit Audit first, so the version bumps sit on a tree that was just checked. ```sh git fetch origin v2/main git worktree add --no-track "../$(basename "$PWD")-$N-audit" \ -b "v2/chore/$N-release-audit" origin/v2/main cd "../$(basename "$PWD")-$N-audit" && npm install # npm: one lockfile covers the root and all four TypeScript workspaces. npm audit --audit-level=high # Python: each server has its own lockfile. `uv` has no audit of its own in the # version pinned here, so the locked requirements go through pip-audit. # Exported to a file first: piped straight in, a failed `uv export` hands # pip-audit empty input, which it reports as clean. And pip-audit exits # non-zero both when it finds something and when it could not run, so a # server counts as audited only when its output holds a verdict. The block # runs in a subshell that exits non-zero if any server was not audited. ( unaudited= for s in fetch git time; do echo "== $s" REQ=$(mktemp) OUT=$(mktemp) if (cd "src/$s" && uv export --frozen --no-emit-project --format requirements-txt) > "$REQ" \ && [ -s "$REQ" ]; then uvx pip-audit --require-hashes --disable-pip -r "$REQ" > "$OUT" 2>&1 cat "$OUT" grep -qE 'No known vulnerabilities found|Found [0-9]+ known vulnerabilit' "$OUT" || { echo "AUDIT DID NOT RUN for $s: no verdict in its output" >&2 unaudited="$unaudited $s" } else echo "EXPORT FAILED for $s: nothing was audited" >&2 unaudited="$unaudited $s" fi rm -f "$REQ" "$OUT" done [ -z "$unaudited" ] || { echo "NOT AUDITED:$unaudited" >&2; exit 1; } ); echo "python audit complete: EXIT=$?" ``` **This step is a report.** Read it; do not let a tool rewrite the tree. The `EXIT=` line says whether every Python server was audited, not whether the audit was clean: `pip-audit`'s findings are in the output above it. ⚠️ **Never `npm audit fix`**, with or without `--force`. `AGENTS.md` **Dependencies** rules it out: it resolves an advisory that has no upward escape by silently downgrading, and the gate does not detect a version going backwards. Each finding gets one of three outcomes, decided by a person: - **A fix inside the declared range**: a direct bump, or an `overrides` entry (npm) or a raised lower bound plus `uv lock` (Python). Each fix is **its own commit**, and a fix to a TypeScript server's runtime dependencies carries a changeset. - **A fix that needs a major**: its own issue and its own PR, not a release-day edit. - **No fix available, or not reachable from the server**: say so in the report, with the reason. Post the report as a comment on the release issue. If fixes were forced, open the PR (`/pr-flow`), body starting `Closes #N`, with the report in it. **If none were, there is no audit PR**: an empty PR is not evidence, the comment is. ### 2b. The TypeScript version bumps: "Version Packages" `version-packages.yml` keeps this PR open whenever `.changeset/` holds pending changesets. Find it: ```sh gh pr list --repo modelcontextprotocol/servers --base v2/main \ --head changeset-release/v2/main --json number,title,url ``` No PR means no TypeScript server has a pending changeset. Check that is true rather than assuming it, since a TypeScript change merged without its changeset releases nothing: ```sh # Refresh first, and read the REMOTE tree: the checkout may be a preparation # branch cut before another PR merged. git fetch origin main v2/main --tags git ls-tree -r --name-only origin/v2/main .changeset/ | grep '\.md$' | grep -vx '.changeset/README.md' # the pending changesets # Since the latest published Release, asked of GitHub: this repo's tags do not # sort into release order (see 5a). PREV=$(gh release view --repo modelcontextprotocol/servers --json tagName --jq .tagName) git log --oneline "$PREV"..origin/v2/main -- \ src/everything src/filesystem src/memory src/sequentialthinking ``` If the second command lists changes to what a server publishes and the first lists no changeset for that server, add the missing changeset through an ordinary PR first. Then, on the PR: 1. **Link it to the release issue, and leave a comment.** The action rewrites the PR's body on every push to `v2/main`, so a `Closes` line there would not survive; this is the exception `AGENTS.md` names. The link lives outside the body, so make it now, with the `addCloseIssueReferences` mutation from `/pr-flow` (step 6), and read it back. On `v2/main` it closes nothing at merge. Then comment `Part of #N`, the textual record that every rewrite leaves alone: ```sh PR=<VERSION_PACKAGES_PR> gh pr comment "$PR" --repo modelcontextprotocol/servers --body "Part of #$N" ``` 2. **Wait for its CI to pass.** The workflow token's push starts no workflow, so the Version Packages workflow dispatches the TypeScript, Python, DCO and Everything diff workflows on `changeset-release/v2/main` after each update. A PR does not list dispatched checks, so the result shows as four commit statuses: `TypeScript (dispatched)`, `Python (dispatched)`, `DCO (dispatched)` and `Everything diff (dispatched)`. The PR may also list `DCO signoff` and the diff as pending or failed with no jobs: GitHub holds the token's `pull_request` runs (#5173), so read the statuses instead. Closing and reopening the PR does not start the CI workflows (that fires `pull_request`; they run on `push`). If a status does not appear, dispatch its workflow by hand: `gh workflow run typescript.yml --ref changeset-release/v2/main` (or `python.yml`, `dco.yml`, `everything-mcp-diff.yml`). 3. **Read it**: each bump matches the semver policy in `RELEASING.md`, each `CHANGELOG.md` entry says what a user of the server needs to know, the consumed changeset files are deleted, and `package-lock.json` records the new versions. 4. **Put `Part of #N` as the body's first line, immediately before the merge**, once nothing else will land on `v2/main` first: any push there regenerates the body and drops the line. ```sh # Read the body first, and edit only on a successful, non-empty read: a # failed read must never replace the generated release notes with the # prefix alone. OLD=$(gh pr view "$PR" --repo modelcontextprotocol/servers --json body --jq .body) \ && [ -n "$OLD" ] \ && printf 'Part of #%s\n\n%s\n' "$N" "$OLD" \ | gh pr edit "$PR" --repo modelcontextprotocol/servers --body-file - \ || echo "body read or edit FAILED — PR body left as it was" >&2 ``` 5. A maintainer merges it, **first checking the line is still the body's first**. If a push regenerated the body in between, repeat step 4: ```sh gh pr view "$PR" --repo modelcontextprotocol/servers \ --json body --jq '.body | split("\n")[0]' # must print: Part of #N ``` ### 2c. The Python version stamps: "Prepare Python Release" Do this **last**, close to the release: it stamps the date it runs on. ```sh gh workflow run prepare-python-release.yml --repo modelcontextprotocol/servers \ --ref v2/main -f issue="$N" ``` It opens a PR that starts with `Closes #N`, on a branch named `v2/chore/<N>-python-calver-<date>`, labeled `v2`, and dispatches the TypeScript, Python and DCO workflows on that branch, as the Version Packages workflow does (not the Everything diff, whose paths it never touches). Wait for its three `(dispatched)` statuses to pass, check that each stamped package really changed, and a maintainer merges it. No PR means no Python server has a shipped change since its last stamp. ⚠️ GitHub only dispatches a workflow that exists on the default branch. Until the first milestone merge carries this one to `main`, do it by hand, which is the same script: ```sh git fetch origin v2/main git worktree add --no-track "../$(basename "$PWD")-$N-calver" \ -b "v2/chore/$N-python-calver-$(date -u +%Y.%-m.%-d)" origin/v2/main cd "../$(basename "$PWD")-$N-calver" && npm install node scripts/prepare-python-release.mjs # prints "name: old -> new" per stamped package git commit -s -am "chore: stamp Python CalVer versions for $(date -u +%Y.%-m.%-d)" ``` then open the PR through `/pr-flow`, body starting `Closes #N`. ## 3. The merge PR: `v2/main` → `main` Open it only when every preparation PR has merged. The merge branch is `v2/main` under another name: cut from `origin/v2/main`, with nothing added. The merge itself is made by GitHub when the PR merges, so `main` gets exactly one merge commit and the PR has no commit of its own. (Merging locally first would put a merge commit on the branch, and merging the PR would then add a second.) ```sh git fetch origin main v2/main git worktree add --no-track "../$(basename "$PWD")-$N-merge" \ -b "v2/chore/$N-release-$MILESTONE" origin/v2/main cd "../$(basename "$PWD")-$N-merge" ``` **The merge's result is the release candidate. Prove it will be `v2/main`'s tree before opening the PR:** ```sh git rev-parse 'origin/v2/main^{tree}' git merge-tree --write-tree origin/main HEAD # must print the same hash, and nothing else ``` `git merge-tree` computes the tree the merge into `main` would produce, without touching the checkout. A different hash, or conflict output, means `main` holds a change `v2/main` does not. That is a change that exists only downstream of the develop branch. Stop and find out what it is, rather than pushing. Open the PR against **`main`**, labeled `v2`. Its body's first line is **`Part of #N`**, not `Closes #N`: ```sh BODY=$(mktemp) cat > "$BODY" <<EOF Part of #$N Milestone merge for $MILESTONE: \`v2/main\` → \`main\`. No commits of its own; the merged tree is \`origin/v2/main\`'s (\`$(git rev-parse --short 'HEAD^{tree}')\`). Release ledger: <link, added in step 4> EOF git push -u origin HEAD gh pr create --repo modelcontextprotocol/servers --base main --label v2 \ --title "Release $MILESTONE: merge v2/main into main" --body-file "$BODY" ``` This is the one PR in the repository that targets `main`. ⚠️ **It is merged with "Create a merge commit", never squashed or rebased.** A squash writes one new commit that `v2/main` does not contain, so the two branches stop sharing history and the next milestone merge conflicts with itself. `main`'s ruleset allows only merge commits, so the other two buttons are not offered. ⚠️ **Do not click "Update branch" on the merge PR.** GitHub always offers it here, because `main` always holds commits the merge branch lacks (its own earlier merge commits), so it does not mean the PR is out of date. Clicking it merges `main` into the merge branch: the PR gains a commit of its own (which fails the required **Source is v2/main** check, `main-source.yml`), `main` receives two merge commits, and the branch head is no longer the commit the ledger names. If it was clicked, put the branch back on its head from before the click: the update merge's **first parent**, which is the commit the ledger
GitHubで見る
この SKILL.md は非常に大きいため、SkillsMP では最初のセクションだけを表示しています。 GitHubで見る