Skip to content

feat(workflows): add mcp-server-deploy reusable workflow - #11

Merged
asachs01 merged 1 commit into
mainfrom
feat/mcp-server-deploy-reusable
May 13, 2026
Merged

feat(workflows): add mcp-server-deploy reusable workflow#11
asachs01 merged 1 commit into
mainfrom
feat/mcp-server-deploy-reusable

Conversation

@asachs01

Copy link
Copy Markdown
Member

Companion to `mcp-server-ci.yml`, finishing the per-repo workflow factorization. CI was already shared; deploy was still 8x copy-paste, which is how we ended up with autotask-mcp silently shipping every release into an orphaned ACA for 3+ weeks.

What this workflow does

  • Logs into Azure via OIDC
  • Deploys the caller's image to `gwp-` by immutable digest
  • Hard-fails if the digest input is empty or not `sha256:`-prefixed
  • Sets `IMAGE_VERSION` env var on the new revision tying it back to release version + git SHA + workflow run ID for forensics

Failure patterns this fixes for the fleet

1. Wrong ACA

Every `-mcp` release deploys to `mcpgw-prod-`. The gateway routes vendor traffic per `VENDOR_URL_` env vars — all of which point to `gwp-`. The `mcpgw-prod-` ACAs are orphaned legacy from a prior naming convention. Releases shipped to them are silent no-ops.

Discovered today (2026-05-13): autotask's `gwp-autotask` had last been manually updated on 2026-04-21. Three weeks of releases went to `mcpgw-prod-autotask` and were never reachable by the gateway.

2. Mutable `:latest` tag

Even with the correct ACA, deploying by `:latest` is racy. The tag can resolve to a stale digest through GHCR edge caches or ACA's image-pull cache. The deploy command "succeeds" but the new revision actually rolls onto the prior image.

This was observed twice today on autotask-mcp PRs #95 (TicketHistory) and #96 (attachment fix): release pipeline reported success on every step, GHCR `:latest` correctly pointed at the new digest, ACA reported the new revision active — but the running container was the prior image. Verified by manual digest-pin override.

Caller contract

In each `*-mcp/release.yml`:

  1. `docker` job must expose its push digest as a job output:
    ```yaml
    docker:

    ...

    outputs:
    digest: ${{ steps.push.outputs.digest }}
    steps:
    - id: push
    uses: docker/build-push-action@...
    ```

  2. `deploy` job becomes a thin caller:
    ```yaml
    deploy:
    needs: [release, docker]
    uses: wyre-technology/.github/.github/workflows/mcp-server-deploy.yml@
    with:
    vendor-slug: autotask
    image-name: ghcr.io/wyre-technology/autotask-mcp
    digest: ${{ needs.docker.outputs.digest }}
    version: ${{ needs.release.outputs.version }}
    secrets: inherit
    ```

Rollout plan

Will be rolled out to autotask-mcp first as the reference call site (its current inline fix from PR #97 gets refactored to use this), then in follow-up PRs to:

  • halopsa-mcp
  • datto-rmm-mcp
  • itglue-mcp
  • huntress-mcp
  • liongard-mcp
  • domotz-mcp
  • cipp-mcp

After full rollout, the `mcpgw-prod-` ACAs become safe to delete — they're orphaned now and won't be touched by any release pipeline. Tracking that cleanup separately.

Test plan

  • YAML parses (`python3 -c "import yaml; yaml.safe_load(open(...))"`)
  • Caller-side validation lands when autotask-mcp gets converted to use this workflow
  • First post-rollout release on each repo lands on `gwp-` with an `@sha256:...` image reference

Canonical deploy workflow for wyre-technology/*-mcp repositories.
Companion to mcp-server-ci.yml, finishing the per-repo workflow
factorization (CI was already shared; deploy was still 8x copy-paste,
which is how we ended up with autotask-mcp silently shipping every
release into an orphaned ACA for 3+ weeks).

What this workflow does:
- Logs into Azure via OIDC
- Deploys the caller's image to gwp-<vendor-slug> by IMMUTABLE digest
- Hard-fails if the digest input is empty or not sha256:-prefixed
- Sets IMAGE_VERSION env var on the new revision tying it back to
  release version + git SHA + workflow run ID for forensics

Failure patterns this fixes for the fleet:
1. Deploying to mcpgw-prod-<vendor> instead of gwp-<vendor>. The
   gateway routes vendor traffic per VENDOR_URL_<VENDOR> env, all of
   which point to gwp-<vendor>. The mcpgw-prod-* ACAs are orphaned
   legacy from a prior naming convention. Releases shipped to them
   are silent no-ops.
2. Deploying by :latest tag. :latest is mutable and can resolve to a
   stale digest through GHCR edge caches or ACA's image-pull cache,
   making deploys "succeed" while actually rolling onto the prior
   image (observed 2026-05-13 with autotask-mcp PRs #95 and #96).

Caller contract (release.yml):
- docker job must expose `outputs.digest:
  ${{ steps.<push-step>.outputs.digest }}`
- deploy job replaces its inline body with `uses:
  wyre-technology/.github/.github/workflows/mcp-server-deploy.yml@<sha>`
  passing vendor-slug, image-name, digest, version. Secrets propagate
  via `secrets: inherit` or explicit forwarding.

Will be rolled out to autotask-mcp first as the reference call site,
then to halopsa-mcp, datto-rmm-mcp, itglue-mcp, huntress-mcp,
liongard-mcp, domotz-mcp, cipp-mcp in follow-up PRs.

See related skills:
- ci-docker-semantic-release-version-race
- mcp-gateway-troubleshooting
@asachs01
asachs01 merged commit c2d6fc8 into main May 13, 2026
1 check passed
@asachs01
asachs01 deleted the feat/mcp-server-deploy-reusable branch May 13, 2026 19:28
@github-project-automation github-project-automation Bot moved this from Todo to Done in MSP Claude Plugins May 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

1 participant