Guide · Code Quality & Release Tooling

MCP Server Changesets — semantic versioning, changelog automation, Version Packages PR

Three Changesets behaviours confuse MCP server maintainers new to the tool: Changesets does not automatically bump package versions or publish to npm — the changeset CLI only creates a markdown file in .changeset/ describing the intended bump (patch/minor/major); the actual version bump and CHANGELOG.md update happen in a separate step (changeset version) that is typically run by the GitHub Actions bot, which opens a "Version Packages" PR that must be manually reviewed and merged before anything is published; the "Version Packages" PR requires a human merge@changesets/action opens the PR but never merges it automatically, so teams that configure the action and then wait for automatic releases will wait forever; and packages in the monorepo must be explicitly listed in .changeset/config.json under the packages key (or use the workspace glob) — packages not in that list are silently ignored by changeset version and changeset publish.

TL;DR

Install @changesets/cli and run npx changeset init. When you make a release-worthy change, run npx changeset, select the packages affected and the bump type (patch/minor/major), and write a summary. Commit the generated .changeset/*.md file. The GitHub Actions bot (@changesets/action) will open a "Version Packages" PR that bumps versions and updates CHANGELOG.md. Merge that PR, then run npx changeset publish to push to npm.

Initializing Changesets in a TypeScript MCP server project

Changesets works for both single-package MCP servers and multi-package monorepos. The initialization creates a .changeset/ directory with a config.json and a README.md explaining the workflow.

# Install Changesets CLI
npm install -D @changesets/cli

# Initialize — creates .changeset/config.json and .changeset/README.md
npx changeset init
// .changeset/config.json — generated by changeset init
{
  "$schema": "https://unpkg.com/@changesets/config/schema.json",
  "changelog": "@changesets/cli/changelog",
  "commit": false,       // if true, changeset version auto-commits the version bump
  "fixed": [],           // packages that always version together (all-or-nothing)
  "linked": [],          // packages that mirror each other's version but release separately
  "access": "restricted", // change to "public" if publishing to npm (not a private registry)
  "baseBranch": "main",
  "updateInternalDependencies": "patch",
  "ignore": []           // package names to never release (e.g., internal tooling packages)
}

// For a public MCP server package, change "access" to "public":
// "access": "public"
// Required for npm packages published without a scope, or scoped packages
// under a public org (e.g., @myorg/mcp-server-search)

For a single-package MCP server repo, the config above works as-is. For a monorepo with multiple MCP server packages (e.g., a collection of domain-specific tools), set access: "public" and add your workspace glob to ensure Changesets discovers all packages.

Creating changesets and the Version Packages PR workflow

The core Changesets workflow for MCP server maintainers: write code, create a changeset, open a PR, merge, and the bot creates the "Version Packages" PR. The "Version Packages" PR must be merged manually — only then is the package ready to publish.

# Step 1: You've added a new MCP tool to your server — time to create a changeset
npx changeset
# Interactive CLI:
# ? Which packages would you like to include? › my-mcp-server
# ? What kind of change is this for my-mcp-server?
#   ◉ patch  (bug fix, backward compatible)
#   ○ minor  (new feature, backward compatible)  ← select this for a new tool
#   ○ major  (breaking change, NOT backward compatible)
# ? Please enter a summary for this change › add web_search tool with DuckDuckGo backend

# This creates a file like .changeset/fuzzy-sharks-dance.md:
# ---
# "my-mcp-server": minor
# ---
#
# Add web_search tool with DuckDuckGo backend
# Supports query, max_results, and safe_search parameters
# Step 2: Commit the changeset file as part of your feature PR
git add .changeset/fuzzy-sharks-dance.md
git commit -m "feat(tools): add web_search tool with DuckDuckGo backend"
git push

# Step 3: Open a PR. The @changesets/action bot comments on the PR
# showing which packages will be bumped and to what version.
# The changeset file is reviewed as part of the PR review.
# "Is this a minor bump? Does the summary accurately describe the change?"
# Step 4: After merging your feature PR, @changesets/action opens a new PR
# titled "Version Packages" (or "chore: version packages" in some configs)
# This PR contains:
# - package.json version bumps (e.g., "version": "1.2.0" → "1.3.0")
# - CHANGELOG.md updates (one entry per changeset, grouped by bump type)
# - Deletion of the consumed .changeset/*.md files

# The "Version Packages" PR aggregates ALL pending changesets.
# If you have 3 feature PRs merged (each with a changeset), the bot
# creates ONE "Version Packages" PR that bumps once for all 3 changes.
# The bump level is the highest across all pending changesets:
# patch + minor + patch = minor bump overall
# Step 5: Review and MERGE the "Version Packages" PR
# After merging, run publish manually (or via a publish workflow):
npx changeset publish

# Or in CI, use the @changesets/action publish step:
# (See the GitHub Actions section below)

The changeset file in .changeset/*.md is the source of truth for what changed and at what semver level. Reviewing it during the feature PR review ensures the changelog entry is accurate before the version bump happens — unlike semantic-release, which infers bump type from commit messages after the fact.

GitHub Actions workflow for Changesets

The @changesets/action GitHub Action handles opening the "Version Packages" PR on every push to main. A separate publish step in the same workflow handles npm publish when the "Version Packages" PR is merged.

# .github/workflows/release.yml
name: Release

on:
  push:
    branches: [main]

concurrency: ${{ github.workflow }}-${{ github.ref }}

jobs:
  release:
    name: Release
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0  # Required for Changesets to compare with previous tags

      - uses: actions/setup-node@v4
        with:
          node-version: '22'
          cache: 'npm'
          registry-url: 'https://registry.npmjs.org'

      - run: npm ci

      - name: Create Release PR or Publish
        uses: changesets/action@v1
        with:
          # When there are pending changesets: opens/updates "Version Packages" PR
          # When "Version Packages" PR is merged: runs `changeset publish`
          publish: npx changeset publish
          title: 'chore: version packages'
          commit: 'chore: version packages'
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
          # NPM_TOKEN: set in GitHub repo Settings → Secrets → Actions
          # Create at npmjs.com → Account → Access Tokens → Generate New Token (Automation)

The fetch-depth: 0 on the checkout step is required because Changesets uses git tags to determine which packages have pending changes. A shallow clone (the default, fetch-depth: 1) hides older tags, causing Changesets to think every package has changes when only some do.

Snapshot releases for testing pre-release MCP server versions

When you need collaborators to test a breaking change before the final release, Changesets snapshot releases publish a pre-release version to npm with an auto-generated version number (e.g., 0.0.0-20260807113045) without consuming the pending changesets or incrementing the stable version. This is useful for testing a new MCP transport protocol version before committing to a major bump.

# Publish a snapshot release for testing (does NOT consume pending changesets)
npx changeset version --snapshot
npx changeset publish --tag next

# This publishes e.g. my-mcp-server@0.0.0-20260807113045 to npm
# Users can install: npm install my-mcp-server@next
# Snapshot releases are tagged "next" (or whatever tag you specify)
# They do NOT appear as the "latest" version on npm

# After testing, discard the version bumps from --snapshot (don't commit them)
# The original .changeset/*.md files are still intact — run a normal release when ready:
npx changeset version  # bumps to the real semver version
npx changeset publish  # publishes as "latest"

For MCP server packages with breaking changes to the tool schema or transport, use the major bump type and add a migration guide section to the changeset markdown file. The content of the changeset markdown beyond the frontmatter --- block appears verbatim in the CHANGELOG.md — so a well-written changeset summary saves users from having to read a commit diff to understand what broke.