Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 35 additions & 49 deletions .github/workflows/mcp-packaging.yml
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -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.<repository_owner>/* 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 }}
118 changes: 118 additions & 0 deletions docs/stackql release process.md
Original file line number Diff line number Diff line change
@@ -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.<repository_owner>/*` 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=<classic PAT with read:org scope>
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)
2 changes: 2 additions & 0 deletions packaging/mcpb/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,5 @@ pypi/src/*.egg-info/
/.venv*
.pypirc
__pycache__/
/.env
.mcpregistry_*
2 changes: 1 addition & 1 deletion packaging/mcpb/pypi/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Loading