-
Notifications
You must be signed in to change notification settings - Fork 71
feat(mcp): Add HTTP header bearer token authentication support #914
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 11 commits
f882e93
60a087f
3869ac4
cd1b390
9f3c707
5591792
954dd1a
ab593dd
5298ade
59ad29a
3b08a87
4c0e44c
dcde4aa
96ed8d4
d0215fd
98d6d26
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,136 @@ | ||
| name: On-Demand Prerelease | ||
|
|
||
| on: | ||
| workflow_dispatch: | ||
| inputs: | ||
| pr: | ||
| description: 'PR Number' | ||
| type: string | ||
| required: true | ||
| comment-id: | ||
| description: 'Comment ID (Optional)' | ||
| type: string | ||
| required: false | ||
|
|
||
| env: | ||
| AIRBYTE_ANALYTICS_ID: ${{ vars.AIRBYTE_ANALYTICS_ID }} | ||
|
|
||
| permissions: | ||
| contents: write | ||
| id-token: write | ||
| pull-requests: write | ||
| issues: write | ||
|
|
||
| jobs: | ||
| resolve-pr: | ||
| name: Set up Workflow | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - name: Resolve workflow variables | ||
| id: vars | ||
| uses: aaronsteers/resolve-ci-vars-action@2e56afab0344bbe03c047dfa39bae559d0291472 # v0.1.6 | ||
|
|
||
| - name: Append comment with job run link | ||
| id: first-comment-action | ||
| uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0 | ||
| with: | ||
| comment-id: ${{ github.event.inputs.comment-id }} | ||
| issue-number: ${{ github.event.inputs.pr }} | ||
| body: | | ||
| > **Prerelease Build Started** | ||
| > | ||
| > Building and publishing prerelease package from this PR... | ||
| > [Check job output.](${{ steps.vars.outputs.run-url }}) | ||
| - name: Checkout to get latest tag | ||
| uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 | ||
| with: | ||
| fetch-depth: 0 | ||
|
|
||
| - name: Compute prerelease version | ||
| id: version | ||
| run: | | ||
| # Get the latest tag version (strip 'v' prefix if present) | ||
| LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0") | ||
| BASE_VERSION=${LATEST_TAG#v} | ||
| # Create a unique prerelease version using PR number and run ID | ||
| # Format: {base}.dev{pr_number}{run_id} (e.g., 0.34.0.dev825123456789) | ||
| PRERELEASE_VERSION="${BASE_VERSION}.dev${{ github.event.inputs.pr }}${{ github.run_id }}" | ||
| echo "version=$PRERELEASE_VERSION" >> $GITHUB_OUTPUT | ||
| echo "Computed prerelease version: $PRERELEASE_VERSION" | ||
| outputs: | ||
| source-repo: ${{ steps.vars.outputs.pr-source-repo-name-full }} | ||
| source-branch: ${{ steps.vars.outputs.pr-source-git-branch }} | ||
| commit-sha: ${{ steps.vars.outputs.pr-source-git-sha }} | ||
| pr-number: ${{ steps.vars.outputs.pr-number }} | ||
| job-run-url: ${{ steps.vars.outputs.run-url }} | ||
| first-comment-id: ${{ steps.first-comment-action.outputs.comment-id }} | ||
| prerelease-version: ${{ steps.version.outputs.version }} | ||
|
|
||
| build-and-publish: | ||
| name: Trigger Publish Workflow | ||
| needs: [resolve-pr] | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - name: Authenticate as GitHub App | ||
| uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4 | ||
| id: get-app-token | ||
| with: | ||
| owner: "airbytehq" | ||
| repositories: "PyAirbyte" | ||
| app-id: ${{ secrets.OCTAVIA_BOT_APP_ID }} | ||
| private-key: ${{ secrets.OCTAVIA_BOT_PRIVATE_KEY }} | ||
| - name: Trigger pypi_publish workflow | ||
| id: dispatch | ||
| uses: the-actions-org/workflow-dispatch@v4 | ||
| with: | ||
| workflow: pypi_publish.yml | ||
| token: ${{ steps.get-app-token.outputs.token }} | ||
| ref: main # Run from main so OIDC attestation matches trusted publisher | ||
| inputs: '{"git_ref": "refs/pull/${{ github.event.inputs.pr }}/head", "version_override": "${{ needs.resolve-pr.outputs.prerelease-version }}", "publish": "true"}' | ||
| wait-for-completion: true | ||
| wait-for-completion-timeout: 30m | ||
| outputs: | ||
| workflow-conclusion: ${{ steps.dispatch.outputs.workflow-conclusion }} | ||
| workflow-url: ${{ steps.dispatch.outputs.workflow-url }} | ||
|
|
||
| post-result-comment: | ||
| name: Write Status to PR | ||
| needs: [resolve-pr, build-and-publish] | ||
| if: always() | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - name: Post success comment | ||
| if: needs.build-and-publish.result == 'success' | ||
| uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0 | ||
| with: | ||
| comment-id: ${{ needs.resolve-pr.outputs.first-comment-id }} | ||
| issue-number: ${{ github.event.inputs.pr }} | ||
| reactions: rocket | ||
| body: | | ||
| > **Prerelease Published to PyPI** | ||
| > | ||
| > Version: `${{ needs.resolve-pr.outputs.prerelease-version }}` | ||
| > [View publish workflow](${{ needs.build-and-publish.outputs.workflow-url }}) | ||
| > | ||
| > Install with: | ||
| > ```bash | ||
| > pip install airbyte==${{ needs.resolve-pr.outputs.prerelease-version }} | ||
| > ``` | ||
| - name: Post failure comment | ||
| if: needs.build-and-publish.result == 'failure' | ||
| uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0 | ||
| with: | ||
| comment-id: ${{ needs.resolve-pr.outputs.first-comment-id }} | ||
| issue-number: ${{ github.event.inputs.pr }} | ||
| reactions: confused | ||
| body: | | ||
| > **Prerelease Build/Publish Failed** | ||
| > | ||
| > The prerelease encountered an error. | ||
| > [Check publish workflow output](${{ needs.build-and-publish.outputs.workflow-url }}) for details. | ||
| > | ||
| > You can still install directly from this PR branch: | ||
| > ```bash | ||
| > pip install 'git+https://github.com/${{ needs.resolve-pr.outputs.source-repo }}.git@${{ needs.resolve-pr.outputs.source-branch }}' | ||
| > ``` | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,6 +4,36 @@ on: | |
| push: | ||
|
|
||
| workflow_dispatch: | ||
| inputs: | ||
| git_ref: | ||
| description: 'Git ref (SHA or branch) to checkout and build' | ||
| required: false | ||
| type: string | ||
| version_override: | ||
| description: 'Version to use (overrides dynamic versioning)' | ||
| required: false | ||
| type: string | ||
| publish: | ||
| description: 'Whether to publish to PyPI (true/false)' | ||
| required: false | ||
| type: string | ||
| default: 'false' | ||
|
|
||
| workflow_call: | ||
| inputs: | ||
| git_ref: | ||
| description: 'Git ref (SHA or branch) to checkout and build' | ||
| required: true | ||
| type: string | ||
| version_override: | ||
| description: 'Version to use (overrides dynamic versioning)' | ||
| required: false | ||
| type: string | ||
| publish: | ||
| description: 'Whether to publish to PyPI' | ||
| required: false | ||
| type: boolean | ||
| default: false | ||
|
|
||
| env: | ||
| AIRBYTE_ANALYTICS_ID: ${{ vars.AIRBYTE_ANALYTICS_ID }} | ||
|
|
@@ -14,8 +44,21 @@ jobs: | |
| steps: | ||
| - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 | ||
| with: | ||
| ref: ${{ inputs.git_ref || github.ref }} | ||
| fetch-depth: 0 | ||
| - uses: hynek/build-and-inspect-python-package@efb823f52190ad02594531168b7a2d5790e66516 # v2.14.0 | ||
| - name: Prepare version override | ||
| id: version | ||
| run: | | ||
|
||
| echo "override=${{ inputs.version_override }}" >> $GITHUB_OUTPUT | ||
| echo "has_override=${{ inputs.version_override != '' }}" >> $GITHUB_OUTPUT | ||
| - name: Build package (with version override) | ||
| if: steps.version.outputs.has_override == 'true' | ||
| uses: hynek/build-and-inspect-python-package@efb823f52190ad02594531168b7a2d5790e66516 # v2.14.0 | ||
| env: | ||
| POETRY_DYNAMIC_VERSIONING_BYPASS: ${{ steps.version.outputs.override }} | ||
| - name: Build package (dynamic version) | ||
| if: steps.version.outputs.has_override != 'true' | ||
| uses: hynek/build-and-inspect-python-package@efb823f52190ad02594531168b7a2d5790e66516 # v2.14.0 | ||
|
|
||
| publish: | ||
| name: Publish to PyPI | ||
|
|
@@ -27,13 +70,16 @@ jobs: | |
| environment: | ||
| name: PyPi | ||
| url: https://pypi.org/p/airbyte | ||
| if: startsWith(github.ref, 'refs/tags/') | ||
| # Publish when: (1) triggered by a tag push, OR (2) called with publish=true (handles both boolean and string) | ||
| if: startsWith(github.ref, 'refs/tags/') || inputs.publish == true || inputs.publish == 'true' | ||
| steps: | ||
| - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 | ||
| with: | ||
| name: Packages | ||
| path: dist | ||
| - name: Upload wheel to release | ||
| # Only upload to GitHub release when triggered by a tag | ||
| if: startsWith(github.ref, 'refs/tags/') | ||
| uses: svenstaro/upload-release-action@81c65b7cd4de9b2570615ce3aad67a41de5b1a13 # latest | ||
| with: | ||
| repo_token: ${{ secrets.GITHUB_TOKEN }} | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,6 +2,7 @@ | |
| """Authentication-related constants and utilities for the Airbyte Cloud.""" | ||
|
|
||
| from airbyte import constants | ||
| from airbyte.mcp._util import get_bearer_token_from_headers | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Devin, Can you create a follow-on issue to refactor slightly... Ideally nothing outside the MCP module would import from the MCP module. That said, this is expedient and doesn't seem to create circular dependencies or side effects (that I can see). So I'm inclined to merge and create follow-on issue for refactoring.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Created follow-on issue: #915 The issue proposes moving |
||
| from airbyte.secrets import SecretString | ||
| from airbyte.secrets.util import get_secret, try_get_secret | ||
|
|
||
|
|
@@ -26,8 +27,23 @@ def resolve_cloud_bearer_token( | |
| input_value: str | SecretString | None = None, | ||
| /, | ||
| ) -> SecretString | None: | ||
| """Get the Airbyte Cloud bearer token from the environment.""" | ||
| return try_get_secret(constants.CLOUD_BEARER_TOKEN_ENV_VAR, default=input_value) | ||
| """Get the Airbyte Cloud bearer token. | ||
|
|
||
| Resolution order: | ||
| 1. Explicit input_value parameter | ||
| 2. HTTP Authorization header (when running as MCP HTTP server) | ||
| 3. AIRBYTE_CLOUD_BEARER_TOKEN environment variable | ||
| """ | ||
| if input_value: | ||
| return SecretString(input_value) | ||
|
|
||
| # Try HTTP header first (for MCP HTTP transport) | ||
| header_token = get_bearer_token_from_headers() | ||
| if header_token: | ||
| return SecretString(header_token) | ||
|
|
||
| # Fall back to environment variable | ||
| return try_get_secret(constants.CLOUD_BEARER_TOKEN_ENV_VAR, default=None) | ||
|
|
||
|
|
||
| def resolve_cloud_api_url( | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📝 [actionlint] reported by reviewdog 🐶
shellcheck reported issue in this script: SC2086:info:7:39: Double quote to prevent globbing and word splitting [shellcheck]