From 2173ea1975f39ba5b5bedf27cc7d89ea60ae4829 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Sat, 25 Jul 2026 00:10:04 +0000 Subject: [PATCH 1/2] ci: authenticate cloud-mcp preview dispatch via GitHub App + pass released version Co-Authored-By: AJ Steers --- .github/workflows/pypi_publish.yml | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pypi_publish.yml b/.github/workflows/pypi_publish.yml index ba842c8d8..088c4d43f 100644 --- a/.github/workflows/pypi_publish.yml +++ b/.github/workflows/pypi_publish.yml @@ -81,7 +81,9 @@ jobs: uses: pypa/gh-action-pypi-publish@v1.13.0 # Prod cloud-mcp is bumped via Dependabot on airbytehq/airbyte-ops-mcp, not here. - # A PyPI publish only refreshes the cloud-mcp preview against the new version. + # A PyPI publish dispatches the just-released version to airbyte-ops-mcp, which + # opens/refreshes a deterministic `airbyte==` bump PR and deploys the + # cloud-mcp preview off it (see `deploy-mcp-command.yml`). deploy_cloud_mcp_preview: name: Deploy Cloud MCP (Preview) needs: publish_to_pypi @@ -89,13 +91,35 @@ jobs: runs-on: ubuntu-latest permissions: {} steps: + - name: Resolve released version + id: version + run: | + if [ "${{ github.event_name }}" = "release" ]; then + raw="${{ github.event.release.tag_name }}" + else + raw="${{ github.event.inputs.version_override }}" + fi + echo "value=${raw#v}" >> "$GITHUB_OUTPUT" + - name: Wait for PyPI availability run: sleep 120 + # Cross-repo dispatch is authenticated with an Octavia GitHub App token + # scoped to airbyte-ops-mcp (the app must be installed there with + # contents: write), matching the pattern used by prerelease-command.yml. + - name: Authenticate as GitHub App + uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4 + id: get-app-token + with: + owner: airbytehq + repositories: airbyte-ops-mcp + app-id: ${{ secrets.OCTAVIA_BOT_APP_ID }} + private-key: ${{ secrets.OCTAVIA_BOT_PRIVATE_KEY }} + - name: Trigger cloud-mcp preview deploy uses: peter-evans/repository-dispatch@28959ce8df70de7be546dd1250a005dd32156697 # v4.0.1 with: - token: ${{ secrets.GITHUB_CI_WORKFLOW_TRIGGER_PAT }} + token: ${{ steps.get-app-token.outputs.token }} repository: airbytehq/airbyte-ops-mcp event-type: deploy-cloud-mcp - client-payload: '{"mcp-server": "cloud-mcp", "preview": "true"}' + client-payload: '{"mcp-server": "cloud-mcp", "preview": "true", "airbyte-version": "${{ steps.version.outputs.value }}"}' From f6451963908096d9afe5c91dd12d2adcf729c058 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Sat, 25 Jul 2026 00:14:35 +0000 Subject: [PATCH 2/2] ci: harden version resolution (env+validate), poll PyPI, scope app token to contents:write Co-Authored-By: AJ Steers --- .github/workflows/pypi_publish.yml | 52 +++++++++++++++++++++++------- 1 file changed, 41 insertions(+), 11 deletions(-) diff --git a/.github/workflows/pypi_publish.yml b/.github/workflows/pypi_publish.yml index 088c4d43f..e87f009eb 100644 --- a/.github/workflows/pypi_publish.yml +++ b/.github/workflows/pypi_publish.yml @@ -81,9 +81,9 @@ jobs: uses: pypa/gh-action-pypi-publish@v1.13.0 # Prod cloud-mcp is bumped via Dependabot on airbytehq/airbyte-ops-mcp, not here. - # A PyPI publish dispatches the just-released version to airbyte-ops-mcp, which - # opens/refreshes a deterministic `airbyte==` bump PR and deploys the - # cloud-mcp preview off it (see `deploy-mcp-command.yml`). + # A PyPI publish dispatches the just-released version to airbyte-ops-mcp, whose + # deploy-mcp-command.yml opens/refreshes a deterministic `airbyte==` + # bump PR and deploys the cloud-mcp preview off it. deploy_cloud_mcp_preview: name: Deploy Cloud MCP (Preview) needs: publish_to_pypi @@ -91,28 +91,58 @@ jobs: runs-on: ubuntu-latest permissions: {} steps: + # Read the raw ref/input via env (not inline `${{ }}` in the script) to + # avoid shell injection, then validate it is a plain PEP 440-ish version + # before it reaches the dispatch payload -- this both fails fast on a + # malformed tag/override and guarantees the value is safe to interpolate + # into the client-payload JSON string below (no quotes/backslashes). - name: Resolve released version id: version + env: + EVENT_NAME: ${{ github.event_name }} + RELEASE_TAG: ${{ github.event.release.tag_name }} + VERSION_OVERRIDE: ${{ github.event.inputs.version_override }} run: | - if [ "${{ github.event_name }}" = "release" ]; then - raw="${{ github.event.release.tag_name }}" + set -euo pipefail + if [ "$EVENT_NAME" = "release" ]; then + raw="$RELEASE_TAG" else - raw="${{ github.event.inputs.version_override }}" + raw="$VERSION_OVERRIDE" fi - echo "value=${raw#v}" >> "$GITHUB_OUTPUT" + version="${raw#v}" + [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+([.a-zA-Z0-9-]*)?$ ]] \ + || { echo "::error::Refusing to dispatch an unexpected version string: '$version'"; exit 1; } + echo "value=$version" >> "$GITHUB_OUTPUT" + # Poll the version-specific PyPI endpoint instead of a fixed sleep so the + # dispatch never races ahead of replication (and fails fast if the just- + # published version never shows up). - name: Wait for PyPI availability - run: sleep 120 + env: + AIRBYTE_VERSION: ${{ steps.version.outputs.value }} + run: | + set -euo pipefail + for _ in $(seq 1 30); do + if curl --fail --silent --show-error \ + "https://pypi.org/pypi/airbyte/${AIRBYTE_VERSION}/json" >/dev/null; then + exit 0 + fi + sleep 10 + done + echo "::error::airbyte==${AIRBYTE_VERSION} was not available on PyPI in time" + exit 1 - # Cross-repo dispatch is authenticated with an Octavia GitHub App token - # scoped to airbyte-ops-mcp (the app must be installed there with - # contents: write), matching the pattern used by prerelease-command.yml. + # Cross-repo dispatch is authenticated with an Octavia GitHub App token, + # scoped to airbyte-ops-mcp and to contents:write only (least privilege; + # repository scoping limits where the token works, not its permission + # set), matching the pattern used by prerelease-command.yml. - name: Authenticate as GitHub App uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4 id: get-app-token with: owner: airbytehq repositories: airbyte-ops-mcp + permission-contents: write app-id: ${{ secrets.OCTAVIA_BOT_APP_ID }} private-key: ${{ secrets.OCTAVIA_BOT_PRIVATE_KEY }}