diff --git a/.github/workflows/mcp-packaging.yml b/.github/workflows/mcp-packaging.yml index f4495603..fe1bfc49 100644 --- a/.github/workflows/mcp-packaging.yml +++ b/.github/workflows/mcp-packaging.yml @@ -1,9 +1,12 @@ -# MCP bundle packaging: builds + smoke-tests the .mcpb bundles, OCI image and -# npm/pypi wrapper artifacts from packaging/mcpb. Validation runs on -# packaging/** PRs; publishing is MANUAL (workflow_dispatch) because the -# consumed release assets (Authenticode-signed exe, notarised .pkg) are -# signed outside CI, so a release event fires before they exist. Flow: +# MCP bundle packaging: builds + smoke-tests the .mcpb bundles and OCI image +# from packaging/mcpb. Validation runs on packaging/** PRs; publishing is +# MANUAL (workflow_dispatch) because the consumed release assets +# (Authenticode-signed exe, notarised .pkg) are signed outside CI, so a +# release event fires before they exist. Flow: # push tag -> create release -> attach signed assets -> dispatch. +# The npm / PyPI wrappers need interactive (2FA) credentials and are +# published from a local clone - see step 7 of docs/stackql release process.md. +# The MCP Registry entry is published here via GitHub Actions OIDC (no secret). name: mcp-packaging @@ -221,52 +224,35 @@ jobs: docker buildx create --use make oci-push VERSION=${{ needs.version.outputs.version }} - # npm / pypi manifests render from the published .sha256 files as run - # artifacts; publishing to npmjs / PyPI stays manual (2FA). - npm-tarball: - name: npm tarball (manual publish artifact) - if: needs.version.outputs.is_publish == 'true' - needs: [version, publish] - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - name: Render manifest + pack - run: make npm-pack VERSION=${{ needs.version.outputs.version }} - - uses: actions/upload-artifact@v7 - with: - name: npm-package - path: packaging/mcpb/npm/*.tgz - - pypi-dist: - name: pypi dist (manual publish artifact) - if: needs.version.outputs.is_publish == 'true' - needs: [version, publish] - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - name: Render manifest + build - run: make pypi-build VERSION=${{ needs.version.outputs.version }} - - uses: actions/upload-artifact@v7 - with: - name: pypi-dist - path: packaging/mcpb/pypi/dist/* - - server-json: - name: render registry server.json + # Publish server.json to the Official MCP Registry. Auth is GitHub Actions + # OIDC: the registry grants io.github./* to OIDC tokens, + # so no secret is needed - just id-token: write. Runs after 'publish' and + # renders from the RELEASE .sha256 assets (not run artifacts) because the + # sign step regenerates checksums before upload. + registry-publish: + name: publish to MCP registry if: needs.version.outputs.is_publish == 'true' needs: [version, publish] runs-on: ubuntu-latest + permissions: + id-token: write + contents: read steps: - uses: actions/checkout@v6 - - name: Gather bundle artifacts (for sha pins) - uses: actions/download-artifact@v8 - with: - pattern: stackql-mcp-* - path: packaging/mcpb/dist - merge-multiple: true - - name: Render server.json - run: make server-json VERSION=${{ needs.version.outputs.version }} - - uses: actions/upload-artifact@v7 - with: - name: server-json - path: packaging/mcpb/registry/server.json + - name: Fetch published bundle checksums from the release + env: + GH_TOKEN: ${{ github.token }} + run: | + mkdir -p dist + gh release download "v${{ needs.version.outputs.version }}" \ + --repo "$GITHUB_REPOSITORY" \ + --pattern 'stackql-mcp-*.mcpb.sha256' \ + --dir dist + - name: Install mcp-publisher + run: | + curl -fsSL "https://github.com/modelcontextprotocol/registry/releases/latest/download/mcp-publisher_linux_amd64.tar.gz" | tar xz mcp-publisher + sudo install -m 0755 mcp-publisher /usr/local/bin/mcp-publisher + - name: Login (GitHub Actions OIDC) + run: mcp-publisher login github-oidc + - name: Publish + run: make registry-publish VERSION=${{ needs.version.outputs.version }} diff --git a/docs/stackql release process.md b/docs/stackql release process.md new file mode 100644 index 00000000..506cd8d8 --- /dev/null +++ b/docs/stackql release process.md @@ -0,0 +1,118 @@ +## StackQL release process + +1. Download Artifacts from Latest Build + +Download the following artifacts from the latest build on the `main` branch including: + +- `amd64-artifact-deb` +- `arm64-artifact-deb` +- `stackql_linux_amd64` +- `stackql_linux_arm64` +- `stackql_windows_amd64` (used in step 2) + +2. Package and Sign Windows Version + +Using the [stackql/stackql-msi](https://github.com/stackql/stackql-msi) project along with a Microsoft Authenticode CodeSigning hardware token, create the windows packages: + +- `stackql_windows_amd64.msi` +- `stackql_windows_amd64.zip` + +3. Package, Sign and Notarize the Multi Arch Darwin Version + +Using a Mac with the correct certificate chain configured (MacInCloud), run the [stackql/stackql-mac-installer](https://github.com/stackql/stackql-mac-installer), upload the package binary `stackql_darwin_multiarch.pkg` to Google Drive. + +Download the signed, notarized package file: + +- `stackql_darwin_multiarch.pkg` + +4. Push a tag and create a release + +Push a tag using the semver, `{major}.{minor}.{build_number}`, for example `0.10.557` + +The `build_number` is the latest successful GitHub Actions build number for the `build` job on a merge to `main` + +``` +git tag v0.10.557 +git push origin v0.10.557 +``` + +Create a release from the tag (set to latest) + +5. Add the following assets to the release: + +- `amd64-artifact-deb` (downloaded in step 1) +- `arm64-artifact-deb` (downloaded in step 1) +- `stackql_linux_amd64.zip` (downloaded in step 1) +- `stackql_linux_arm64.zip` (downloaded in step 1) +- `stackql_windows_amd64.msi` (built in step 2) +- `stackql_windows_amd64.zip` (built in step 2) +- `stackql_darwin_multiarch.pkg` (built in step 3) + +6. Build and push MCPB assets to the release + +Invoke the `mcp-packaging` workflow in [stackql/stackql](https://github.com/stackql/stackql) + +7. Publish the MCP wrapper packages (manual last mile) + +The `mcp-packaging` workflow attaches the `.mcpb` bundles (and `.sha256` files) to the release, pushes the multi-arch OCI image to Docker Hub, and publishes the Official MCP Registry entry (via GitHub Actions OIDC - see 7c) automatically. Only the npm and PyPI wrappers need interactive (2FA) credentials, so they cannot be automated end to end and are published from a local clone using the steps below (a Linux/WSL/macOS shell, from the repo root). + +The render targets (`make npm-pack` / `make pypi-build`) consume the published `.sha256` release assets, so run them only after step 6 completes. The renderers also stamp the version into `packaging/mcpb/npm/package.json` and `packaging/mcpb/pypi/pyproject.toml` in place - commit or revert the stamps afterwards. + +7a. npm (`@stackql/mcp-server`) + +Requires an `npm login` session as a user with publish rights on the `@stackql` scope; the publish prompts for an OTP. + +``` +cd packaging/mcpb +make npm-pack VERSION=X.Y.Z +cd npm +npm publish stackql-mcp-server-X.Y.Z.tgz --access public +``` + +7b. PyPI (`stackql-mcp-server`) + +Requires a PyPI API token with upload rights on the project; twine username is `__token__`, password is the token. On Debian/Ubuntu (including WSL) `pip install` outside a venv is blocked by PEP 668, hence the venv. + +``` +cd packaging/mcpb +python3 -m venv ~/.venvs/pypi-pub && source ~/.venvs/pypi-pub/bin/activate +pip install --upgrade build twine +make pypi-build VERSION=X.Y.Z +python -m twine check pypi/dist/* +python -m twine upload pypi/dist/* +``` + +7c. Official MCP Registry (`io.github.stackql/stackql-mcp`) - automated, manual fallback below + +The `registry-publish` job in `mcp-packaging.yml` publishes this automatically: it authenticates with `mcp-publisher login github-oidc` (the registry grants `io.github./*` to GitHub Actions OIDC tokens, so no secret is needed) and renders server.json from the release `.sha256` assets. Nothing to do unless the job fails or an out-of-band publish is needed - in that case, the manual procedure: + +Requires the latest `mcp-publisher` CLI and a classic GitHub PAT (scope `read:org` only, no repo scopes) created by a `stackql` org Owner at https://github.com/settings/tokens/new. The server.json renderer reads the four `.sha256` files from the local `dist/` directory, so download the published checksum files from the release first. + +Install/upgrade the CLI (linux amd64 shown; assets exist per platform): + +``` +curl -fsSL "https://github.com/modelcontextprotocol/registry/releases/latest/download/mcp-publisher_linux_amd64.tar.gz" | tar xz mcp-publisher +sudo install -m 0755 mcp-publisher /usr/local/bin/mcp-publisher +``` + +Publish: + +``` +cd packaging/mcpb +for t in linux-x64 linux-arm64 windows-x64 darwin-universal; do + curl -fsSL -o dist/stackql-mcp-$t.mcpb.sha256 \ + "https://github.com/stackql/stackql/releases/download/vX.Y.Z/stackql-mcp-$t.mcpb.sha256" +done +export MCP_GITHUB_TOKEN= +mcp-publisher login github +make registry-publish VERSION=X.Y.Z +``` + +Gotchas (all hit during the v0.10.557 release): + +- Use the latest `mcp-publisher` from https://github.com/modelcontextprotocol/registry/releases. The current schema version is baked into the binary, so a stale CLI fails with a misleading "deprecated schema detected" error even when `server.template.json` pins the correct schema. +- Log in with a classic PAT via `MCP_GITHUB_TOKEN` as above, not the interactive device flow. The registry grants the `io.github.stackql/*` namespace only to `stackql` org Owners, and it checks the role via `GET /user/memberships/orgs`. The device-flow login is a GitHub App user token ("MCP Registry Login") that cannot see the org membership unless that app is installed on the org, so it 403s with a misleading hint about public org membership - the PAT path avoids all of that. +- The registry JWT minted at login is short-lived - run `make registry-publish` immediately after `mcp-publisher login github`. +- Old `mcp-publisher` versions (pre-1.2) drop `.mcpregistry_*` token files in the working directory (gitignored); current versions store the token in `~/.config/mcp-publisher/token.json`. + +8. Push the same release version tag to [stackql/releases.stackql.io](https://github.com/stackql/releases.stackql.io) diff --git a/packaging/mcpb/.gitignore b/packaging/mcpb/.gitignore index 49eea2d9..2d9da4d1 100644 --- a/packaging/mcpb/.gitignore +++ b/packaging/mcpb/.gitignore @@ -14,3 +14,5 @@ pypi/src/*.egg-info/ /.venv* .pypirc __pycache__/ +/.env +.mcpregistry_* diff --git a/packaging/mcpb/pypi/pyproject.toml b/packaging/mcpb/pypi/pyproject.toml index 252e5292..227e5311 100644 --- a/packaging/mcpb/pypi/pyproject.toml +++ b/packaging/mcpb/pypi/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "stackql-mcp-server" -version = "0.10.500" +version = "0.10.557" description = "StackQL MCP server - SQL-native query and provisioning engine for cloud infrastructure, served over MCP. Downloads the signed stackql binary on first run." readme = "README.md" license = "MIT"