Skip to content

Release v1.0.0: reject bad filters instead of widening the query #12

Release v1.0.0: reject bad filters instead of widening the query

Release v1.0.0: reject bad filters instead of widening the query #12

Workflow file for this run

name: Publish container image to GHCR
# Builds the remote-HTTP (`node server/index.js --http`) image OFF-host on
# GitHub-hosted runners and pushes it to GHCR, so the single-CPU IWAC-docker host
# only ever does a lightweight `docker pull` (no on-host build contention).
#
# Triggers on a version tag (v*), or manually. Version tags also publish the
# Claude Desktop / stdio .mcpb bundles and the .agents research skill zip as
# GitHub release assets, then publish the server to the official MCP Registry
# (registry.modelcontextprotocol.io) as io.github.fmadore/iwac-mcp-server.
#
# Ordering is deliberate: typecheck + lint + the full hermetic test suite run
# FIRST, then the release assets are packed, then the image is smoke-tested in a
# container, and only THEN is anything pushed. A red test or a broken pack
# script can no longer ship an image (previously the image — including
# `latest` — was pushed before any verification ran, and no tests ran on the
# tag path at all).
on:
push:
tags:
- "v*"
workflow_dispatch:
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }} # fmadore/iwac-mcp-server
# Single source of truth for the Node major used to build and test releases.
# Keep in step with ci.yml, mcpb/Dockerfile and mcpb/package.json `engines`.
# NOTE: this is the BUILD baseline only. The shipped .mcpb still targets
# node18 (scripts/bundle.mjs) and declares `runtimes.node: ">=18.0.0"`
# (manifest.json), because Claude Desktop runs the extension on its OWN
# bundled Node — raising that floor without knowing Desktop's version would
# stop the extension installing. Those two move together, or not at all.
NODE_VERSION: 24
# Pin a specific mcp-publisher release (e.g. "v1.4.0") for reproducible,
# supply-chain-safer publishes; "latest" keeps the old auto-tracking
# behaviour. TODO: set a concrete version after checking
# https://github.com/modelcontextprotocol/registry/releases.
MCP_PUBLISHER_VERSION: latest
jobs:
build-and-push:
runs-on: ubuntu-latest
permissions:
contents: write
packages: write
id-token: write # MCP Registry publish via GitHub OIDC (no secret needed)
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Set up Node
uses: actions/setup-node@v6
with:
node-version: ${{ env.NODE_VERSION }}
cache: npm
cache-dependency-path: mcpb/package-lock.json
- name: Install dependencies
working-directory: mcpb
run: npm ci
# Release gate: the same hermetic suite ci.yml runs on PRs. Nothing below
# (image push, release assets, registry publish) happens if this fails.
- name: Typecheck, lint, build, and test
working-directory: mcpb
run: |
npm run typecheck
npm run lint
npm run build
npm test
- name: Check tag matches package.json/manifest.json versions
if: startsWith(github.ref, 'refs/tags/v')
env:
TAG: ${{ github.ref_name }}
run: |
node -e '
const fs = require("fs");
const tag = process.env.TAG.replace(/^v/, "");
const pkg = JSON.parse(fs.readFileSync("mcpb/package.json", "utf8")).version;
const man = JSON.parse(fs.readFileSync("mcpb/manifest.json", "utf8")).version;
if (tag !== pkg || tag !== man) {
console.error(`version mismatch: tag ${tag}, package.json ${pkg}, manifest.json ${man}`);
process.exit(1);
}
console.log(`versions consistent: ${tag}`);
'
- name: Build MCPB and skill release assets
if: startsWith(github.ref, 'refs/tags/v')
working-directory: mcpb
run: npm run release
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Log in to GHCR
uses: docker/login-action@v4
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Derive image tags and labels
id: meta
uses: docker/metadata-action@v6
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
# `latest` only moves on version tags — a manual workflow_dispatch
# from a branch must not repoint it.
tags: |
type=semver,pattern={{version}}
type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/v') }}
type=sha,format=short
- name: Build image (local, for smoke test)
uses: docker/build-push-action@v7
with:
context: mcpb
file: mcpb/Dockerfile
platforms: linux/amd64
load: true
tags: iwac-mcp-smoke:ci
cache-from: type=gha
cache-to: type=gha,mode=max
# The Dockerfile's own comment says "a missing binding is a runtime crash,
# not a build error" — so actually boot the container before pushing:
# /health must come up, and /mcp must enforce the bearer token.
- name: Container smoke test
run: |
docker run -d --rm --name iwac-smoke \
-e IWAC_MCP_BEARER_TOKEN=ci-smoke -p 8000:8000 iwac-mcp-smoke:ci
ok=""
for i in $(seq 1 30); do
if curl -fsS http://127.0.0.1:8000/health >/dev/null 2>&1; then ok=1; break; fi
sleep 1
done
if [ -z "$ok" ]; then
echo "container never became healthy"; docker logs iwac-smoke || true; exit 1
fi
code=$(curl -s -o /dev/null -w '%{http_code}' -X POST http://127.0.0.1:8000/mcp)
if [ "$code" != "401" ]; then
echo "expected 401 without bearer token, got $code"; exit 1
fi
docker stop iwac-smoke
- name: Push image (linux/amd64)
uses: docker/build-push-action@v7
with:
context: mcpb
file: mcpb/Dockerfile
platforms: linux/amd64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Upload GitHub release assets
if: startsWith(github.ref, 'refs/tags/v')
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ github.ref_name }}
run: |
gh release view "$TAG" >/dev/null 2>&1 || gh release create "$TAG" --title "$TAG" --notes "Release $TAG"
gh release upload "$TAG" \
mcpb/iwac-mcp-server-windows.mcpb \
mcpb/iwac-mcp-server-macos.mcpb \
iwac-mcp-skill.zip \
--clobber
# MCP Registry publish. server.json is generated HERE, in the same job
# that packed and uploaded the .mcpb assets, so the embedded fileSha256
# hashes always match the exact uploaded bytes. Registry versions are
# immutable: re-running this workflow for an already-published tag fails
# at this step by design (re-packed zips hash differently) — bump the
# version instead of re-publishing.
- name: Generate server.json
if: startsWith(github.ref, 'refs/tags/v')
env:
TAG: ${{ github.ref_name }}
run: node mcpb/scripts/make-server-json.mjs "$TAG"
- name: Install mcp-publisher
if: startsWith(github.ref, 'refs/tags/v')
run: |
os=$(uname -s | tr '[:upper:]' '[:lower:]')
arch=$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/')
if [ "$MCP_PUBLISHER_VERSION" = "latest" ]; then
url="https://github.com/modelcontextprotocol/registry/releases/latest/download/mcp-publisher_${os}_${arch}.tar.gz"
else
url="https://github.com/modelcontextprotocol/registry/releases/download/${MCP_PUBLISHER_VERSION}/mcp-publisher_${os}_${arch}.tar.gz"
fi
curl -fL "$url" | tar xz mcp-publisher
- name: Publish to MCP Registry
if: startsWith(github.ref, 'refs/tags/v')
run: |
./mcp-publisher login github-oidc
./mcp-publisher publish