name: Build and Release on: push: tags: - 'v*' workflow_dispatch: inputs: version: description: 'Version tag (e.g., v1.32.0)' required: true type: string jobs: # ── Build binaries ─────────────────────────────────────────────────────────── build-macos: runs-on: macos-latest steps: - uses: actions/checkout@v4 - 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 - name: Package binary run: | mkdir -p dist/mcp-mt5-quant-macos cp target/release/mt5-quant dist/mcp-mt5-quant-macos/ cp -r config dist/mcp-mt5-quant-macos/ cp README.md dist/mcp-mt5-quant-macos/ cp -r docs dist/mcp-mt5-quant-macos/ cd dist tar -czf mcp-mt5-quant-macos-arm64.tar.gz mcp-mt5-quant-macos - name: Upload artifact uses: actions/upload-artifact@v4 with: name: macos-binary path: dist/mcp-mt5-quant-macos-arm64.tar.gz build-linux: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - 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 - name: Package binary run: | mkdir -p dist/mcp-mt5-quant-linux cp target/release/mt5-quant dist/mcp-mt5-quant-linux/ cp -r config dist/mcp-mt5-quant-linux/ cp README.md dist/mcp-mt5-quant-linux/ cp -r docs dist/mcp-mt5-quant-linux/ cd dist tar -czf mcp-mt5-quant-linux-x64.tar.gz mcp-mt5-quant-linux - name: Upload artifact uses: actions/upload-artifact@v4 with: name: linux-binary path: dist/mcp-mt5-quant-linux-x64.tar.gz # ── Cargo Publish ──────────────────────────────────────────────────────────── cargo-publish: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Install Rust uses: dtolnay/rust-toolchain@stable - name: Cache Cargo uses: actions/cache@v4 with: path: | ~/.cargo/registry ~/.cargo/git target key: cargo-publish-${{ hashFiles('Cargo.lock') }} restore-keys: cargo-publish- - name: Publish to crates.io run: cargo publish --no-verify --allow-dirty env: CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} # ── GitHub Release ─────────────────────────────────────────────────────────── release: needs: [build-macos, build-linux, cargo-publish] runs-on: ubuntu-latest permissions: contents: write env: RELEASE_TAG: ${{ github.event.inputs.version || github.ref_name }} steps: - uses: actions/checkout@v4 - name: Download macOS artifact uses: actions/download-artifact@v4 with: name: macos-binary path: dist - name: Download Linux artifact uses: actions/download-artifact@v4 with: name: linux-binary path: dist - name: Create GitHub Release uses: softprops/action-gh-release@v2 with: 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 draft: false prerelease: false generate_release_notes: true 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 binary uses: actions/download-artifact@v4 with: name: macos-binary path: dist - name: Build MCP package run: | mkdir -p mcp-package/mcp-server/bin 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/ 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: ${{ env.RELEASE_TAG }} files: mcp-mt5-quant-macos-arm64.tar.gz env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Upload MCP package artifact uses: actions/upload-artifact@v4 with: name: mcp-package path: mcp-mt5-quant-macos-arm64.tar.gz # ── 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: 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 with: name: mcp-package path: . - name: Install mcp-publisher id: install-publisher run: | # Fetch the download URL for linux_amd64 directly from the release assets API # (asset name format changed in v1.6.0 — look it up rather than constructing it) URL=$(curl -sf \ -H "Accept: application/vnd.github+json" \ "https://api.github.com/repos/modelcontextprotocol/registry/releases/latest" \ | python3 -c " import sys, json r = json.load(sys.stdin) tag = r['tag_name'] assets = r.get('assets', []) match = next((a['browser_download_url'] for a in assets if 'linux' in a['name'] and 'amd64' in a['name'] and a['name'].endswith('.tar.gz') and 'sbom' not in a['name'] and 'sigstore' not in a['name']), None) if match: print(match) else: print(f'https://github.com/modelcontextprotocol/registry/releases/download/{tag}/mcp-publisher_linux_amd64.tar.gz', file=sys.stderr) sys.exit(1) " 2>/dev/null) if [[ -z "$URL" ]]; then echo "installed=false" >> "$GITHUB_OUTPUT" echo "Could not resolve mcp-publisher download URL" else echo "Downloading: $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 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: | echo "=== Authenticating with GitHub OIDC ===" mcp-publisher login github-oidc || { echo "OIDC login failed" exit 1 } 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: Summary run: | echo "" echo "╔══════════════════════════════════════════════════════════════╗" echo "║ Release ${RELEASE_TAG} complete! " echo "╚══════════════════════════════════════════════════════════════╝" echo "" echo "GitHub Release:" echo " https://github.com/masdevid/mt5-quant/releases/tag/${RELEASE_TAG}" echo "" echo "MCP Registry: io.github.masdevid/mt5-quant" echo "" echo "── MCP Client Registration ──────────────────────────────────" echo "" echo "Claude Code:" echo " claude mcp add io.github.masdevid/mt5-quant -- ~/.local/bin/mt5-quant" echo "" echo "Windsurf (~/.codeium/windsurf/mcp_config.json):" echo ' {"mcpServers": {"io.github.masdevid/mt5-quant": {"command": "~/.local/bin/mt5-quant"}}}' echo "" echo "Cursor / VS Code (~/.cursor/mcp.json):" echo ' {"mcpServers": {"mt5-quant": {"command": "~/.local/bin/mt5-quant"}}}' echo "" echo "─────────────────────────────────────────────────────────────"