diff --git a/.claude/commands/release.md b/.claude/commands/release.md new file mode 100644 index 0000000..35233ed --- /dev/null +++ b/.claude/commands/release.md @@ -0,0 +1,114 @@ +--- +description: Release a new version of mt5-quant — bump, changelog, tag, push +--- + +# /release — MT5-Quant Release Workflow + +Automates the full release cycle: version bump → changelog → git tag → GitHub push → CI triggers build + MCP publish. + +## Usage + +``` +/release [patch|minor|major|X.Y.Z] +``` + +Default is `patch` if no argument is given. + +--- + +## Step 1 — Check current version and state + +```bash +grep '^version' Cargo.toml | head -1 +git status --short +git log --oneline -5 +``` + +## Step 2 — Count tools (update docs if needed) + +```bash +grep -r "pub fn tool_" src/tools/definitions/ | wc -l +``` + +If the count changed since the last release, update: +- `README.md` — "MCP Tools (N)" section header and description +- `docs/MCP_TOOLS.md` — "documents X of Y total tools" line + +## Step 3 — Run the release script + +```bash +bash scripts/release.sh patch +``` + +Replace `patch` with `minor`, `major`, or an explicit version like `1.33.0`. + +The script will: +1. Bump `Cargo.toml` version +2. Update `server.json` version + download URL (SHA256 = TBD, set by CI) +3. Generate a `CHANGELOG.md` entry from `git log` since the last tag +4. Run `cargo check` to verify the build +5. Commit all changes +6. Create an annotated git tag +7. Push branch + tag → triggers GitHub Actions + +## Step 4 — Monitor GitHub Actions + +```bash +gh run list --limit 5 +``` + +Or open: https://github.com/masdevid/mt5-quant/actions + +The CI pipeline will: +- Build macOS arm64 + Linux x64 binaries +- Create the GitHub Release with both artifacts +- Build the MCP installable package +- Compute the real SHA256 and commit it back to `server.json` +- Attempt to publish to the MCP Registry + +## Step 5 — Install updated binary locally + +```bash +cp target/release/mt5-quant ~/.local/bin/mt5-quant +``` + +Or rebuild from the release tag: + +```bash +cargo build --release && cp target/release/mt5-quant ~/.local/bin/mt5-quant +``` + +## Step 6 — Verify MCP registration + +```bash +claude mcp list +~/.local/bin/mt5-quant --help 2>/dev/null || echo "binary works" +``` + +--- + +## Troubleshooting + +**MCP publish failed in CI?** + +```bash +# Download latest mcp-publisher +curl -L "https://github.com/modelcontextprotocol/registry/releases/latest/download/mcp-publisher_linux_amd64.tar.gz" | tar xz +./mcp-publisher login github +./mcp-publisher publish +``` + +**Need to re-run CI without a new tag?** + +```bash +gh workflow run release.yml -f version=v1.32.0 +``` + +**Rollback a bad release?** + +```bash +git tag -d v1.32.0 +git push origin :refs/tags/v1.32.0 +# Then delete the GitHub Release via web UI or: +gh release delete v1.32.0 +``` diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index aaf44d6..50105ef 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,11 +7,13 @@ on: workflow_dispatch: inputs: version: - description: 'Version tag (e.g., v1.31.0)' + description: 'Version tag (e.g., v1.32.0)' required: true type: string jobs: + # ── Build binaries ─────────────────────────────────────────────────────────── + build-macos: runs-on: macos-latest steps: @@ -20,6 +22,16 @@ jobs: - name: Install Rust uses: dtolnay/rust-toolchain@stable + - name: Cache Cargo + uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: macos-cargo-${{ hashFiles('Cargo.lock') }} + restore-keys: macos-cargo- + - name: Build release binary run: cargo build --release @@ -47,6 +59,16 @@ jobs: - name: Install Rust uses: dtolnay/rust-toolchain@stable + - name: Cache Cargo + uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: linux-cargo-${{ hashFiles('Cargo.lock') }} + restore-keys: linux-cargo- + - name: Build release binary run: cargo build --release @@ -66,11 +88,15 @@ jobs: name: linux-binary path: dist/mcp-mt5-quant-linux-x64.tar.gz + # ── GitHub Release ─────────────────────────────────────────────────────────── + release: needs: [build-macos, build-linux] runs-on: ubuntu-latest permissions: contents: write + env: + RELEASE_TAG: ${{ github.event.inputs.version || github.ref_name }} steps: - uses: actions/checkout@v4 @@ -86,11 +112,11 @@ jobs: name: linux-binary path: dist - - name: Create or Update GitHub Release + - name: Create GitHub Release uses: softprops/action-gh-release@v2 with: - tag_name: ${{ github.event.inputs.version || github.ref_name }} - name: ${{ github.event.inputs.version || github.ref_name }} + tag_name: ${{ env.RELEASE_TAG }} + name: ${{ env.RELEASE_TAG }} files: | dist/mcp-mt5-quant-macos-arm64.tar.gz dist/mcp-mt5-quant-linux-x64.tar.gz @@ -100,15 +126,23 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # ── MCP Package ────────────────────────────────────────────────────────────── + # Builds the installable MCP package (binary + config + docs), + # computes its SHA256, and uploads to the release. + build-mcp-package: needs: release runs-on: macos-latest permissions: contents: write + outputs: + sha256: ${{ steps.compute-sha256.outputs.sha256 }} + env: + RELEASE_TAG: ${{ github.event.inputs.version || github.ref_name }} steps: - uses: actions/checkout@v4 - - name: Download macOS artifact + - name: Download macOS binary uses: actions/download-artifact@v4 with: name: macos-binary @@ -116,36 +150,27 @@ jobs: - name: Build MCP package run: | - # Create MCP package structure mkdir -p mcp-package/mcp-server/bin - - # Extract binary from the tar.gz - cd dist - tar -xzf mcp-mt5-quant-macos-arm64.tar.gz - cp mcp-mt5-quant-macos/mt5-quant ../mcp-package/mcp-server/bin/ - cd .. - - # Copy config and docs + cd dist && tar -xzf mcp-mt5-quant-macos-arm64.tar.gz && cd .. + cp dist/mcp-mt5-quant-macos/mt5-quant mcp-package/mcp-server/bin/ cp -r config mcp-package/ cp -r docs mcp-package/ cp README.md mcp-package/ cp server.json mcp-package/ - - # Create tar.gz - cd mcp-package - tar -czf ../mcp-mt5-quant-macos-arm64.tar.gz . - cd .. - - # Calculate SHA256 - shasum -a 256 mcp-mt5-quant-macos-arm64.tar.gz | awk '{print $1}' > mcp-sha256.txt - echo "MCP Package SHA256: $(cat mcp-sha256.txt)" + cd mcp-package && tar -czf ../mcp-mt5-quant-macos-arm64.tar.gz . && cd .. + + - name: Compute SHA256 + id: compute-sha256 + run: | + SHA256=$(shasum -a 256 mcp-mt5-quant-macos-arm64.tar.gz | awk '{print $1}') + echo "sha256=$SHA256" >> "$GITHUB_OUTPUT" + echo "MCP Package SHA256: $SHA256" - name: Upload MCP package to release uses: softprops/action-gh-release@v2 with: - tag_name: ${{ github.event.inputs.version || github.ref_name }} - files: | - mcp-mt5-quant-macos-arm64.tar.gz + tag_name: ${{ env.RELEASE_TAG }} + files: mcp-mt5-quant-macos-arm64.tar.gz env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -155,14 +180,81 @@ jobs: name: mcp-package path: mcp-mt5-quant-macos-arm64.tar.gz - mcp-publish: + # ── Update server.json SHA256 ──────────────────────────────────────────────── + # Commits the computed SHA256 back to master so server.json always + # reflects the latest published package hash. + + update-server-json: needs: build-mcp-package runs-on: ubuntu-latest permissions: - contents: read - id-token: write # Required for GitHub OIDC authentication + contents: write steps: - uses: actions/checkout@v4 + with: + ref: master + fetch-depth: 1 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Patch server.json + env: + PKG_SHA256: ${{ needs.build-mcp-package.outputs.sha256 }} + RELEASE_TAG: ${{ github.event.inputs.version || github.ref_name }} + run: | + VERSION="${RELEASE_TAG#v}" + echo "VERSION=$VERSION" >> "$GITHUB_ENV" + VERSION="$VERSION" PKG_SHA256="$PKG_SHA256" python3 - <<'PYEOF' + import json, os, re + + sha256 = os.environ['PKG_SHA256'] + version = os.environ['VERSION'] + + with open('server.json') as f: + data = json.load(f) + + data['version'] = version + for pkg in data.get('packages', []): + pkg['version'] = version + pkg['fileSha256'] = sha256 + if 'identifier' in pkg: + pkg['identifier'] = re.sub( + r'/v[0-9]+\.[0-9]+\.[0-9]+/', + f'/v{version}/', + pkg['identifier'] + ) + + with open('server.json', 'w') as f: + json.dump(data, f, indent=2) + f.write('\n') + + print(f"Patched: version={version}, sha256={sha256[:16]}...") + PYEOF + + - name: Commit server.json + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add server.json + if git diff --cached --quiet; then + echo "server.json unchanged — no commit needed" + else + git commit -m "ci: update server.json SHA256 for v${VERSION} [skip ci]" + git push origin master + echo "Committed server.json update" + fi + + # ── MCP Registry Publish ───────────────────────────────────────────────────── + + mcp-publish: + needs: [build-mcp-package, update-server-json] + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write # Required for GitHub OIDC + steps: + - uses: actions/checkout@v4 + with: + ref: master # Pull master so server.json has the correct SHA256 - name: Download MCP package uses: actions/download-artifact@v4 @@ -171,78 +263,120 @@ jobs: path: . - name: Install mcp-publisher + id: install-publisher run: | - # Download mcp-publisher from correct registry repository - curl -fsSL -o mcp-publisher.tar.gz "https://github.com/modelcontextprotocol/registry/releases/download/v1.0.0/mcp-publisher_1.0.0_linux_amd64.tar.gz" || { - echo "⚠️ Failed to download mcp-publisher - MCP publish will be skipped" - echo "Install manually: https://github.com/modelcontextprotocol/registry/releases" - exit 0 - } - tar -xzf mcp-publisher.tar.gz mcp-publisher - chmod +x mcp-publisher - sudo mv mcp-publisher /usr/local/bin/ - rm mcp-publisher.tar.gz + # Resolve latest release tag from GitHub API + LATEST_TAG=$(curl -sf \ + -H "Accept: application/vnd.github+json" \ + "https://api.github.com/repos/modelcontextprotocol/registry/releases/latest" \ + | python3 -c "import sys,json; print(json.load(sys.stdin)['tag_name'])" \ + 2>/dev/null || echo "v1.0.0") + + VERSION="${LATEST_TAG#v}" + URL="https://github.com/modelcontextprotocol/registry/releases/download/${LATEST_TAG}/mcp-publisher_${VERSION}_linux_amd64.tar.gz" + echo "Downloading mcp-publisher ${LATEST_TAG} from: $URL" + + if curl -fsSL -o mcp-publisher.tar.gz "$URL"; then + tar -xzf mcp-publisher.tar.gz 2>/dev/null || true + BINARY=$(find . -maxdepth 3 -name "mcp-publisher" ! -path "./.git/*" | head -1) + if [[ -n "$BINARY" && -f "$BINARY" ]]; then + chmod +x "$BINARY" + sudo mv "$BINARY" /usr/local/bin/mcp-publisher + echo "installed=true" >> "$GITHUB_OUTPUT" + echo "Installed: $(mcp-publisher --version 2>/dev/null || echo 'ok')" + else + echo "installed=false" >> "$GITHUB_OUTPUT" + echo "Binary not found in archive" + fi + rm -f mcp-publisher.tar.gz + else + echo "installed=false" >> "$GITHUB_OUTPUT" + echo "Download failed: $URL" + fi + + - name: Validate server.json before publish + id: validate + run: | + python3 - <<'PYEOF' + import json, sys + + with open('server.json') as f: + data = json.load(f) + + print(f" name: {data.get('name')}") + print(f" version: {data.get('version')}") + + ok = True + for pkg in data.get('packages', []): + sha = pkg.get('fileSha256', '') + ident = pkg.get('identifier', '') + print(f" sha256: {sha}") + print(f" url: {ident}") + if sha in ('', 'TBD_CI_WILL_UPDATE', 'TBD_UPDATED_BY_CI'): + print('ERROR: fileSha256 is a placeholder — publish aborted') + ok = False + + sys.exit(0 if ok else 1) + PYEOF - name: Publish to MCP Registry + if: steps.install-publisher.outputs.installed == 'true' && steps.validate.outcome == 'success' run: | - # Check if mcp-publisher is available - if ! command -v mcp-publisher &> /dev/null; then - echo "⚠️ mcp-publisher not found - skipping automated MCP publish" - exit 0 - fi - - echo "=== MCP Registry Publish ===" - # Use GitHub OIDC for authentication (no token needed) - mcp-publisher login github-oidc || echo "⚠️ Login failed" - - # Publish the server (may fail due to mcp-publisher bug with registryType) - mcp-publisher publish || { - echo "" - echo "╔══════════════════════════════════════════════════════════════════╗" - echo "║ ⚠️ AUTOMATED MCP PUBLISH FAILED - MANUAL ACTION REQUIRED ║" - echo "╚══════════════════════════════════════════════════════════════════╝" - echo "" - echo "The release was created successfully, but MCP registry publish failed." - echo "This is a known issue with mcp-publisher's registryType handling." - echo "" - echo "To publish manually:" - echo " 1. Download mcp-publisher:" - echo " curl -L \"https://github.com/modelcontextprotocol/registry/releases/download/v1.0.0/mcp-publisher_1.0.0_$(uname -s | tr '[:upper:]' '[:lower:]')_amd64.tar.gz\" | tar xz mcp-publisher" - echo " 2. Login: ./mcp-publisher login github" - echo " 3. Publish: ./mcp-publisher publish" - echo "" - echo "Or use the MCP Registry web UI: https://registry.modelcontextprotocol.io" - echo "" - exit 0 # Don't fail the workflow + echo "=== Authenticating with GitHub OIDC ===" + mcp-publisher login github-oidc || { + echo "OIDC login failed" + exit 1 } - - echo "✓ Published to MCP Registry" + + echo "=== Publishing ===" + mcp-publisher publish && echo "✓ Published to MCP Registry" || { + echo "" + echo "══════════════════════════════════════════════════════════" + echo " AUTOMATED MCP PUBLISH FAILED — manual steps:" + echo "══════════════════════════════════════════════════════════" + echo " 1. Download: https://github.com/modelcontextprotocol/registry/releases" + echo " 2. ./mcp-publisher login github" + echo " 3. ./mcp-publisher publish" + echo " Web UI: https://registry.modelcontextprotocol.io" + echo "══════════════════════════════════════════════════════════" + exit 0 # Don't fail the workflow — release succeeded + } + + - name: Skip notice + if: steps.install-publisher.outputs.installed != 'true' + run: | + echo "mcp-publisher not installed — skipping automated publish" + echo "Manual: https://registry.modelcontextprotocol.io" + + # ── Release Summary ────────────────────────────────────────────────────────── release-info: needs: [mcp-publish] runs-on: ubuntu-latest + env: + RELEASE_TAG: ${{ github.event.inputs.version || github.ref_name }} steps: - - name: Display Release Commands + - name: Summary run: | echo "" - echo "╔════════════════════════════════════════════════════════════════╗" - echo "║ 🎉 RELEASE COMPLETED SUCCESSFULLY! 🎉 ║" - echo "╚════════════════════════════════════════════════════════════════╝" + echo "╔══════════════════════════════════════════════════════════════╗" + echo "║ Release ${RELEASE_TAG} complete! " + echo "╚══════════════════════════════════════════════════════════════╝" echo "" - echo "📦 GitHub Release: https://github.com/masdevid/mt5-quant/releases/tag/${{ github.event.inputs.version || github.ref_name }}" - echo "📦 MCP Registry: io.github.masdevid/mt5-quant" + echo "GitHub Release:" + echo " https://github.com/masdevid/mt5-quant/releases/tag/${RELEASE_TAG}" echo "" - echo "═══════════════════════════════════════════════════════════════════" - echo " MCP CLIENT REGISTRATION COMMANDS" - echo "═══════════════════════════════════════════════════════════════════" + echo "MCP Registry: io.github.masdevid/mt5-quant" echo "" - echo "▶️ CLAUDE CODE:" - echo " claude mcp add io.github.masdevid/mt5-quant -- ~/.local/bin/mt5-quant" + echo "── MCP Client Registration ──────────────────────────────────" echo "" - echo "▶️ WINDSURF:" - echo ' echo \'{"mcpServers": {"io.github.masdevid/mt5-quant": {"command": "~/.local/bin/mt5-quant", "disabled": false, "registry": "io.github.masdevid/mt5-quant"}}}\' >> ~/.codeium/windsurf/mcp_config.json' + echo "Claude Code:" + echo " claude mcp add io.github.masdevid/mt5-quant -- ~/.local/bin/mt5-quant" echo "" - echo "▶️ CURSOR / VSCODE:" - echo ' mkdir -p ~/.cursor && echo \'{"mcpServers": {"mt5-quant": {"command": "~/.local/bin/mt5-quant"}}}\' > ~/.cursor/mcp.json' + echo "Windsurf (~/.codeium/windsurf/mcp_config.json):" + echo ' {"mcpServers": {"io.github.masdevid/mt5-quant": {"command": "~/.local/bin/mt5-quant"}}}' echo "" - echo "═══════════════════════════════════════════════════════════════════" + echo "Cursor / VS Code (~/.cursor/mcp.json):" + echo ' {"mcpServers": {"mt5-quant": {"command": "~/.local/bin/mt5-quant"}}}' + echo "" + echo "─────────────────────────────────────────────────────────────" diff --git a/.windsurf/workflows/release.md b/.windsurf/workflows/release.md index 06c450c..bb1e047 100644 --- a/.windsurf/workflows/release.md +++ b/.windsurf/workflows/release.md @@ -1,209 +1,113 @@ --- -description: Release workflow - bump version, build, and publish MCP server +description: Release workflow — bump version, build, tag, push, publish MCP tags: [release, publish, workflow] --- # MT5-Quant Release Workflow -Steps to release a new version of the MCP server. +One-command release: `bash scripts/release.sh [patch|minor|major|X.Y.Z]` -## 1. Pre-Release Checklist +--- -- [ ] All features implemented and tested -- [ ] Documentation updated (README.md, MCP_TOOLS.md) -- [ ] Tool count verified: `grep -r "pub fn tool_" src/tools/definitions/ | wc -l` -- [ ] Version bumped in Cargo.toml -- [ ] Build passes: `cargo build --release` +## 0. Pre-release checklist -## 2. Update Documentation +- [ ] Features implemented and tested +- [ ] Docs updated (README.md, docs/MCP_TOOLS.md) with correct tool count +- [ ] `cargo check` passes + +Check tool count: // turbo ```bash grep -r "pub fn tool_" src/tools/definitions/ | wc -l ``` -Update these files with correct tool counts: -- `README.md` - header and "MCP Tools (N)" section -- `docs/MCP_TOOLS.md` - "documents X of Y total tools" line +--- -## 3. Bump Version - -Edit `Cargo.toml` and update the version: -```toml -version = "X.Y.Z" -``` - -## 4. Build Release +## 1. Run the release script // turbo ```bash -bash scripts/build-release.sh +bash scripts/release.sh patch ``` -This creates: `dist/mcp-mt5-quant-{platform}.tar.gz` +Accepts: `patch` · `minor` · `major` · `1.32.0` · `v1.32.0` -Calculate SHA256 for server.json: -```bash -shasum -a 256 dist/mcp-mt5-quant-macos-arm64.tar.gz -``` +The script handles: +- Version bump in `Cargo.toml` +- `server.json` version + download URL update +- `CHANGELOG.md` entry generation from git log +- `cargo check` verification +- Release commit + annotated git tag +- `git push` → triggers GitHub Actions -## 5. Update MCP Server Configuration +--- -Update both `server.json` files with: -- New version -- New SHA256 hash from step 4 -- Updated download URL -- Tool count in description - -Files to update: -- `server.json` -- `mcp-package/server.json` - -Then copy the release package: -```bash -cp dist/mcp-mt5-quant-macos-arm64.tar.gz mcp-package/ -``` - -## 6. Create Git Tag & Commit - -```bash -git add . -git commit -m "Release vX.Y.Z - brief description" -git tag vX.Y.Z -git push origin vX.Y.Z -``` - -## 7. Create GitHub Release (Manual) - -1. Go to GitHub → Releases → Draft new release -2. Choose tag: `vX.Y.Z` -3. Release title: `vX.Y.Z` -4. Attach binary: `dist/mcp-mt5-quant-macos-arm64.tar.gz` -5. Publish release - -## 8. Install Binary to System Path - -Install the binary to a single location for all MCP clients: - -```bash -# Create local bin if needed -mkdir -p ~/.local/bin - -# Install binary -cp target/release/mt5-quant ~/.local/bin/ - -# Or system-wide (requires sudo) -# sudo cp target/release/mt5-quant /usr/local/bin/ -``` - -**Installation Path:** `~/.local/bin/mt5-quant` (single location for all clients) - -## 9. MCP Registration - -Use the installed binary path (not project directory): - -### Claude Code -```bash -claude mcp add mt5-quant -- ~/.local/bin/mt5-quant -``` - -### Windsurf -Edit `~/.codeium/windsurf/mcp_config.json`: -```json -{ - "mcpServers": { - "mt5-quant": { - "command": "~/.local/bin/mt5-quant" - } - } -} -``` - -### VS Code / Cursor -Edit `~/.cursor/mcp.json` or `.vscode/mcp.json`: -```json -{ - "mcpServers": { - "mt5-quant": { - "command": "~/.local/bin/mt5-quant" - } - } -} -``` - -## 10. MCP Client Registration - -After installing the binary, register with your MCP clients: - -### Windsurf +## 2. Monitor CI // turbo ```bash -# Add to Windsurf MCP config -cat >> ~/.codeium/windsurf/mcp_config.json << 'EOF' -{ - "mcpServers": { - "io.github.masdevid/mt5-quant": { - "command": "~/.local/bin/mt5-quant", - "disabled": false, - "registry": "io.github.masdevid/mt5-quant" - } - } -} -EOF +gh run list --limit 3 ``` -### Claude Code +CI pipeline stages: +1. **build-macos** / **build-linux** — parallel Rust builds with cache +2. **release** — creates GitHub Release with both binaries +3. **build-mcp-package** — packages binary + config + docs, computes SHA256 +4. **update-server-json** — commits real SHA256 back to `server.json` on master +5. **mcp-publish** — publishes to MCP Registry via GitHub OIDC +6. **release-info** — prints registration commands for all MCP clients + +--- + +## 3. Install locally // turbo ```bash -# Register with Claude Code -claude mcp add io.github.masdevid/mt5-quant -- ~/.local/bin/mt5-quant - -# Or with custom name -claude mcp add mt5-quant -- ~/.local/bin/mt5-quant +cp target/release/mt5-quant ~/.local/bin/mt5-quant ``` -### VS Code / Cursor +--- + +## 4. Verify // turbo ```bash -# Add to Cursor MCP config -mkdir -p ~/.cursor -cat > ~/.cursor/mcp.json << 'EOF' -{ - "mcpServers": { - "mt5-quant": { - "command": "~/.local/bin/mt5-quant", - "env": { - "MT5_MCP_HOME": "~/.config/mt5-quant" - } - } - } -} -EOF -``` - -## 11. Post-Release Verification - -// turbo -```bash -# Test the binary -./target/release/mt5-quant --help - -# Verify tool count -./target/release/mt5-quant 2>&1 | head -20 - -# Check MCP registration claude mcp list ``` -## Quick Release Commands +--- + +## Manual MCP publish (if CI publish fails) + +// turbo +```bash +# Get latest mcp-publisher +LATEST=$(curl -sf https://api.github.com/repos/modelcontextprotocol/registry/releases/latest | python3 -c "import sys,json; print(json.load(sys.stdin)['tag_name'])") +VERSION="${LATEST#v}" +curl -fsSL -o pub.tar.gz "https://github.com/modelcontextprotocol/registry/releases/download/${LATEST}/mcp-publisher_${VERSION}_$(uname -s | tr '[:upper:]' '[:lower:]')_amd64.tar.gz" +tar -xzf pub.tar.gz && chmod +x mcp-publisher +./mcp-publisher login github +./mcp-publisher publish +``` + +--- + +## Re-trigger CI without a new tag + +// turbo +```bash +gh workflow run release.yml -f version=v1.32.0 +``` + +--- + +## Rollback ```bash -# Full release cycle -vim Cargo.toml # bump version -bash scripts/build-release.sh -git add . && git commit -m "Release vX.Y.Z" -git tag vX.Y.Z && git push origin vX.Y.Z +# Remove tag locally and remotely +git tag -d v1.32.0 +git push origin :refs/tags/v1.32.0 +# Delete GitHub Release +gh release delete v1.32.0 --yes ``` diff --git a/scripts/release.sh b/scripts/release.sh new file mode 100755 index 0000000..123bf17 --- /dev/null +++ b/scripts/release.sh @@ -0,0 +1,200 @@ +#!/usr/bin/env bash +# scripts/release.sh — Automate version bump, changelog, git tag, and push +# +# Usage: +# bash scripts/release.sh patch # 1.31.0 → 1.31.1 +# bash scripts/release.sh minor # 1.31.0 → 1.32.0 +# bash scripts/release.sh major # 1.31.0 → 2.0.0 +# bash scripts/release.sh 1.32.0 # explicit version +# bash scripts/release.sh v1.32.0 # with v prefix + +set -euo pipefail +cd "$(dirname "$0")/.." + +RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m'; BOLD='\033[1m'; NC='\033[0m' +info() { echo -e "${BLUE}▶ $*${NC}"; } +ok() { echo -e "${GREEN}✓ $*${NC}"; } +warn() { echo -e "${YELLOW}⚠ $*${NC}"; } +die() { echo -e "${RED}✗ $*${NC}" >&2; exit 1; } +hr() { echo -e "${BLUE}────────────────────────────────────────${NC}"; } + +# ── Prerequisites ───────────────────────────────────────────────────────────── + +command -v cargo >/dev/null 2>&1 || die "cargo not found" +command -v python3 >/dev/null 2>&1 || die "python3 not found" + +# ── Current version ─────────────────────────────────────────────────────────── + +current=$(grep '^version' Cargo.toml | head -1 | sed 's/.*"\(.*\)".*/\1/') +[[ -n "$current" ]] || die "Could not parse version from Cargo.toml" +info "Current version: ${BOLD}$current${NC}" + +# ── Compute new version ─────────────────────────────────────────────────────── + +bump="${1:-patch}" +IFS='.' read -r major minor patch_v <<< "$current" + +case "$bump" in + major) new="${major+1}.0.0"; new="$((major + 1)).0.0" ;; + minor) new="${major}.$((minor + 1)).0" ;; + patch) new="${major}.${minor}.$((patch_v + 1))" ;; + v*.*.*) new="${bump#v}" ;; + *.*.*) new="$bump" ;; + *) die "Usage: $0 [patch|minor|major|X.Y.Z]" ;; +esac + +# Validate semver +[[ "$new" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || die "Invalid version: $new" + +hr +info "Releasing: ${BOLD}$current → $new${NC}" +hr + +# ── Confirm ─────────────────────────────────────────────────────────────────── + +read -rp "Proceed with release v${new}? [y/N] " confirm +[[ "$confirm" =~ ^[Yy]$ ]] || die "Aborted" + +# ── Check git state ─────────────────────────────────────────────────────────── + +info "Checking git state..." +if ! git diff --quiet 2>/dev/null || ! git diff --cached --quiet 2>/dev/null; then + die "Working tree has uncommitted changes — commit or stash first" +fi +current_branch=$(git rev-parse --abbrev-ref HEAD) +info "Branch: $current_branch" + +# Check tag doesn't already exist +if git rev-parse "v${new}" >/dev/null 2>&1; then + die "Tag v${new} already exists" +fi +ok "Git state clean" + +# ── 1. Bump Cargo.toml ──────────────────────────────────────────────────────── + +info "Bumping Cargo.toml..." +if [[ "$(uname)" == "Darwin" ]]; then + sed -i '' "s/^version = \"${current}\"/version = \"${new}\"/" Cargo.toml +else + sed -i "s/^version = \"${current}\"/version = \"${new}\"/" Cargo.toml +fi + +# Update Cargo.lock +cargo metadata --no-deps --format-version 1 >/dev/null 2>&1 || true +ok "Cargo.toml → $new" + +# ── 2. Update server.json ───────────────────────────────────────────────────── + +info "Updating server.json..." +NEW_VERSION="$new" python3 - <<'PYEOF' +import json, os, re, sys + +version = os.environ['NEW_VERSION'] + +with open('server.json') as f: + data = json.load(f) + +data['version'] = version + +for pkg in data.get('packages', []): + pkg['version'] = version + # Update download URL to point at new version tag + if 'identifier' in pkg: + pkg['identifier'] = re.sub( + r'/v[0-9]+\.[0-9]+\.[0-9]+/', + f'/v{version}/', + pkg['identifier'] + ) + # SHA256 is computed by CI after building — placeholder signals this + pkg['fileSha256'] = 'TBD_CI_WILL_UPDATE' + +with open('server.json', 'w') as f: + json.dump(data, f, indent=2) + f.write('\n') + +print(f" server.json version={version}, identifier URL updated") +PYEOF +ok "server.json → $new (SHA256 updated by CI)" + +# ── 3. Generate CHANGELOG entry ─────────────────────────────────────────────── + +info "Generating CHANGELOG entry..." +prev_tag=$(git describe --tags --abbrev=0 2>/dev/null || echo "") +today=$(date +%Y-%m-%d) + +{ + echo "## [$new] — $today" + echo "" + if [[ -n "$prev_tag" ]]; then + git log "${prev_tag}..HEAD" --pretty=format:"- %s" --no-merges \ + | grep -v "^- ci:" \ + | grep -v "^- chore:" \ + | grep -v "^$" \ + || echo "- Minor improvements and bug fixes" + else + echo "- Initial public release" + fi + echo "" +} > /tmp/release_entry.md + +if [[ -f CHANGELOG.md ]]; then + # Insert after the first header line + tmp=$(mktemp) + head -1 CHANGELOG.md > "$tmp" + echo "" >> "$tmp" + cat /tmp/release_entry.md >> "$tmp" + tail -n +2 CHANGELOG.md >> "$tmp" + mv "$tmp" CHANGELOG.md +else + { + echo "# Changelog" + echo "" + cat /tmp/release_entry.md + } > CHANGELOG.md +fi +rm -f /tmp/release_entry.md +ok "CHANGELOG.md updated" + +# ── 4. Verify build compiles ────────────────────────────────────────────────── + +info "Verifying build (cargo check)..." +cargo check --quiet 2>&1 || die "cargo check failed — fix errors before releasing" +ok "Build check passed" + +# ── 5. Commit ───────────────────────────────────────────────────────────────── + +info "Creating release commit..." +git add Cargo.toml Cargo.lock server.json CHANGELOG.md +git commit -m "release: v${new} + +- Version bump ${current} → ${new} +- server.json identifier URL updated (SHA256 set by CI after build) +- CHANGELOG.md updated" +ok "Release commit created" + +# ── 6. Tag ──────────────────────────────────────────────────────────────────── + +info "Creating annotated tag v${new}..." +git tag -a "v${new}" -m "Release v${new}" +ok "Tagged v${new}" + +# ── 7. Push ─────────────────────────────────────────────────────────────────── + +info "Pushing to GitHub..." +git push origin "$current_branch" +git push origin "v${new}" +ok "Pushed — GitHub Actions triggered" + +# ── Done ────────────────────────────────────────────────────────────────────── + +hr +echo "" +echo -e "${GREEN}${BOLD} Release v${new} kicked off!${NC}" +echo "" +echo -e " Actions: https://github.com/masdevid/mt5-quant/actions" +echo -e " Release: https://github.com/masdevid/mt5-quant/releases/tag/v${new}" +echo "" +echo -e " ${YELLOW}CI will compute the MCP package SHA256 and${NC}" +echo -e " ${YELLOW}commit it back to server.json automatically.${NC}" +echo "" +hr