mt5-quant 1.32.0

MCP server for MT5 strategy development on macOS/Linux
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

  # ── 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

      - 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 "─────────────────────────────────────────────────────────────"