Skip to content

fix(mcp): make Cloud credential guidance execution-mode-aware#1089

Merged
Aaron ("AJ") Steers (aaronsteers) merged 21 commits into
mainfrom
devin/1784914782-mcp-mode-aware-cred-guidance
Jul 24, 2026
Merged

fix(mcp): make Cloud credential guidance execution-mode-aware#1089
Aaron ("AJ") Steers (aaronsteers) merged 21 commits into
mainfrom
devin/1784914782-mcp-mode-aware-cred-guidance

Conversation

@aaronsteers

@aaronsteers Aaron ("AJ") Steers (aaronsteers) commented Jul 24, 2026

Copy link
Copy Markdown
Member

Summary

Cloud MCP tools told users to "set the AIRBYTE_CLOUD_* environment variables" to authenticate. That guidance is wrong under the hosted (HTTP) MCP transport, where credentials arrive as request headers, not server-side env vars. This makes the credential/workspace guidance execution-mode-aware and moves it into self-rendering exception classes that both PyAirbyte and the Ops MCP can share.

Mode detection is a dependency-light process-global flag — no FastMCP in the credential/guidance path, since FastMCP is becoming an optional extra and core code must not import it there:

  • The flag and its accessors live in airbyte/constants.py: set_hosted_mcp_mode() / is_hosted_mcp_mode(), default False. They're in constants.py (a stdlib-only leaf module) deliberately — exceptions.py already imports from constants, so reading the flag there adds no new import edge and avoids the module-import cycle that a dedicated _util submodule introduced.
  • The hosted HTTP entrypoint airbyte/mcp/http_main.py calls set_hosted_mcp_mode() at startup. The stdio/local path never sets it, so an unset flag ⇒ not hosted. No token/header/request inspection is used to infer mode.

Guidance lives in two new self-rendering exception classes in airbyte/exceptions.py (call sites just raise — no inline message/guidance strings). Each computes mode-aware guidance in __post_init__, reading only is_hosted_mcp_mode() plus the header/env-var constants (no FastMCP, no MCP-layer import):

class AirbyteNoCloudCredentialsError(PyAirbyteInputError):
    guidance: str | None = None
    _allow_bearer: bool = True   # client-creds-only callers (e.g. connector-QA) set False
    _env_vars: bool = True       # False => do not suggest env vars
    # hosted => Authorization / X-Airbyte-Cloud-Client-Id + -Secret headers
    # local  => bearer_token or client_id+client_secret (args or AIRBYTE_CLOUD_* env vars)

class AirbyteMissingWorkspaceContextError(PyAirbyteInputError):
    guidance: str | None = None
    # hosted => X-Airbyte-Workspace-Id header (or workspace_id param)
    # local  => AIRBYTE_CLOUD_WORKSPACE_ID env var (or workspace_id param)

The underscore-prefixed control fields are excluded from the rendered error text, and the explicit guidance: str | None = None field makes it unambiguous that mode-aware guidance is applied by default (the inherited PyAirbyteInputError.guidance is an unannotated class attr, not a dataclass field default). _credentials.py raises AirbyteNoCloudCredentialsError(_env_vars=env_vars); mcp/cloud.py raises AirbyteMissingWorkspaceContextError. The old get_mcp_credential_guidance() helper in _tool_utils.py is removed.

Static tool help / field descriptions (baked at import, can't vary per request) are reworded to mention both paths and name the exact headers/env vars via constants: CLOUD_AUTH_TIP_TEXT, WORKSPACE_ID_TIP_TEXT (airbyte/mcp/cloud.py), and the "Cloud operations" line in MCP_SERVER_INSTRUCTIONS (airbyte/mcp/server.py).

Import cycle / CodeQL: exceptions.py used to type AirbyteError.workspace via a TYPE_CHECKING import of airbyte.cloud.workspaces.CloudWorkspace, which placed exceptions on the _credentialsexceptionscloud_credentials module cycle that CodeQL surfaces once this PR edits _credentials.py. That import is now replaced with a local _WorkspaceWithUrl Protocol — structural typing for the single workspace_url attribute the error needs, so any object exposing it (e.g. CloudWorkspace) satisfies it without an ancestor or import. exceptions therefore no longer imports airbyte.cloud at all, which removes the cycle edge; CodeQL is clean.

Out of scope (intentionally unchanged): CloudClientConfig.from_env / CloudWorkspace.from_env docstrings (env-var by definition), and all non-credential env-var text.

Test plan

  • ruff format --check . and ruff check . clean; import airbyte clean.
  • tests/unit_tests/test_exceptions.py covers both classes in hosted + local modes (by toggling the shared flag), asserts _env_vars=False drops env-var guidance, and that control fields don't leak into rendered text; test_cloud_credentials.py updated for the new wording. The Ops MCP consumes the same shared exceptions and flag.

Link to Devin session: https://app.devin.ai/sessions/3ef501472b3d49ca831f588f36f95b29
Requested by: Aaron ("AJ") Steers (@aaronsteers)

Co-Authored-By: AJ Steers <aj@airbyte.io>
Copilot AI review requested due to automatic review settings July 24, 2026 17:43
@devin-ai-integration

Copy link
Copy Markdown
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@devin-ai-integration
devin-ai-integration Bot marked this pull request as ready for review July 24, 2026 17:43
@github-actions

Copy link
Copy Markdown

👋 Greetings, Airbyte Team Member!

Here are some helpful tips and reminders for your convenience.

💡 Show Tips and Tricks

Testing This PyAirbyte Version

You can test this version of PyAirbyte using the following:

# Run PyAirbyte CLI from this branch:
uvx --from 'git+https://github.com/airbytehq/PyAirbyte.git@devin/1784914782-mcp-mode-aware-cred-guidance' pyairbyte --help

# Install PyAirbyte from this branch for development:
pip install 'git+https://github.com/airbytehq/PyAirbyte.git@devin/1784914782-mcp-mode-aware-cred-guidance'

PR Slash Commands

Airbyte Maintainers can execute the following slash commands on your PR:

  • /fix-pr - Fixes most formatting and linting issues
  • /uv-lock - Updates uv.lock file
  • /test-pr - Runs tests with the updated PyAirbyte
  • /prerelease - Builds and publishes a prerelease version to PyPI
📚 Show Repo Guidance

Helpful Resources

Community Support

Questions? Join the #pyairbyte channel in our Slack workspace.

📝 Edit this welcome message.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Open in Devin Review

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates PyAirbyte’s MCP Cloud credential guidance so it’s execution-mode-aware: hosted (HTTP) MCP requests should instruct users to provide credentials/workspace via request headers, while local/stdio usage should continue to reference AIRBYTE_CLOUD_* environment variables. It also adjusts static help text that cannot vary per-request, and adds unit tests covering the new behavior.

Changes:

  • Added hosted-request detection + centralized, mode-aware credential guidance helpers in airbyte/mcp/_tool_utils.py.
  • Updated MCP Cloud tool tips/instructions and improved runtime error guidance to reference the correct credential path (headers vs env vars).
  • Added/updated unit tests for hosted detection and updated credential guidance wording.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
airbyte/mcp/_tool_utils.py Adds is_hosted_mcp_request() and get_mcp_credential_guidance() to produce transport-aware guidance.
airbyte/mcp/cloud.py Updates Cloud tool help text and uses the new guidance helper in the missing-workspace error.
airbyte/mcp/server.py Updates the server “Cloud operations” instructions to mention hosted-header auth.
airbyte/cloud/_credentials.py Makes generic credential guidance non-prescriptive about env vars only (mentions hosted headers too).
tests/unit_tests/test_mcp_tool_utils.py Adds tests for hosted detection and for both hosted/local guidance strings.
tests/unit_tests/test_cloud_credentials.py Updates expected guidance string to match new wording.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread airbyte/mcp/cloud.py
Comment thread airbyte/mcp/server.py Outdated
…dance

Co-Authored-By: AJ Steers <aj@airbyte.io>
Copilot AI review requested due to automatic review settings July 24, 2026 17:47
@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Cloud credential and workspace failures now use dedicated exceptions with guidance that varies between hosted MCP HTTP and local/stdio execution. Hosted mode is set during HTTP startup, and MCP cloud instructions and tests reflect header- and environment-based configuration.

Changes

Hosted MCP credential guidance

Layer / File(s) Summary
Transport detection and guidance
airbyte/_util/mcp_mode.py, airbyte/_util/meta.py, airbyte/mcp/http_main.py, airbyte/mcp/_tool_utils.py
Hosted MCP mode can be set and queried, and the HTTP entrypoint enables it before server startup.
Cloud error guidance contracts
airbyte/exceptions.py, airbyte/cloud/_credentials.py, airbyte/mcp/cloud.py
Dedicated exceptions generate mode-specific credential and workspace guidance, and cloud resolution raises them when context is missing.
Cloud guidance and validation
airbyte/mcp/cloud.py, airbyte/mcp/server.py, tests/unit_tests/*
MCP instructions and credential expectations describe hosted headers or local environment variables, with parametrized exception guidance tests.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant HTTPMain
  participant MCPCloud
  participant CloudExceptions
  participant MCPClient
  HTTPMain->>MCPCloud: initialize hosted MCP mode
  MCPClient->>MCPCloud: request cloud operation
  MCPCloud->>CloudExceptions: raise credential or workspace exception
  CloudExceptions-->>MCPClient: return mode-specific guidance
Loading

Possibly related PRs

Suggested reviewers: copilot

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 87.50% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: Cloud credential guidance now varies by MCP execution mode.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch devin/1784914782-mcp-mode-aware-cred-guidance

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
tests/unit_tests/test_mcp_tool_utils.py (1)

92-112: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Could we cover the RuntimeError fallback?

is_hosted_mcp_request() explicitly converts request-context RuntimeErrors to False, but this parametrization only covers token/header values. Adding a side_effect=RuntimeError case would protect the intended local/stdio fallback.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit_tests/test_mcp_tool_utils.py` around lines 92 - 112, Add a
parametrized case to test_is_hosted_mcp_request where the mocked request-context
accessor raises RuntimeError, and assert is_hosted_mcp_request() returns False.
Use side_effect=RuntimeError on the appropriate get_access_token or
get_http_headers patch while preserving the existing token/header cases.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@airbyte/mcp/cloud.py`:
- Around line 46-56: Update CLOUD_AUTH_TIP_TEXT and WORKSPACE_ID_TIP_TEXT in
airbyte/mcp/cloud.py (lines 46-56) to consistently document the exact hosted
authentication headers and local alternatives: AIRBYTE_CLOUD_BEARER_TOKEN or the
client-credential pair with workspace ID. Update the server guidance in
airbyte/mcp/server.py (lines 113-116) to use the same wording, including
AIRBYTE_CLOUD_BEARER_TOKEN, so it matches get_mcp_credential_guidance().

---

Nitpick comments:
In `@tests/unit_tests/test_mcp_tool_utils.py`:
- Around line 92-112: Add a parametrized case to test_is_hosted_mcp_request
where the mocked request-context accessor raises RuntimeError, and assert
is_hosted_mcp_request() returns False. Use side_effect=RuntimeError on the
appropriate get_access_token or get_http_headers patch while preserving the
existing token/header cases.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 44d402c1-3c8f-4509-9a06-5c1f4915b136

📥 Commits

Reviewing files that changed from the base of the PR and between bacbe7b and 1d6ab3f.

📒 Files selected for processing (6)
  • airbyte/cloud/_credentials.py
  • airbyte/mcp/_tool_utils.py
  • airbyte/mcp/cloud.py
  • airbyte/mcp/server.py
  • tests/unit_tests/test_cloud_credentials.py
  • tests/unit_tests/test_mcp_tool_utils.py

Comment thread airbyte/mcp/cloud.py

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Comment thread airbyte/mcp/_tool_utils.py Outdated
Comment thread airbyte/mcp/cloud.py Outdated
Comment thread tests/unit_tests/test_mcp_tool_utils.py Outdated
@github-code-quality

github-code-quality Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Code Coverage Overview

Languages: Python

Python / code-coverage/pytest-fast

The overall coverage in commit 622c268 in the devin/1784914782-mcp... branch is 68%. The coverage in commit d9f652f in the main branch is 65%.

Show a code coverage summary of the most impacted files.
File main d9f652f devin/1784914782-mcp... 622c268 +/-
airbyte/mcp/_tool_utils.py 72% 84% +12%
airbyte/mcp/registry.py 53% 70% +17%
airbyte/mcp/server.py 69% 88% +19%
airbyte/mcp/_arg_resolvers.py 13% 44% +31%
airbyte/mcp/int...c_history_ui.py 0% 36% +36%
airbyte/mcp/int...hared_models.py 0% 81% +81%
airbyte/cloud/models.py 0% 91% +91%
airbyte/mcp/int..._registry_ui.py 0% 92% +92%
airbyte/mcp/int...nc_status_ui.py 0% 97% +97%
airbyte/mcp/_guards.py 0% 100% +100%

Python / code-coverage/pytest-no-creds

The overall coverage in commit 622c268 in the devin/1784914782-mcp... branch is 68%. The coverage in commit d9f652f in the main branch is 65%.

Show a code coverage summary of the most impacted files.
File main d9f652f devin/1784914782-mcp... 622c268 +/-
airbyte/mcp/_tool_utils.py 72% 84% +12%
airbyte/mcp/registry.py 53% 70% +17%
airbyte/mcp/server.py 69% 88% +19%
airbyte/mcp/_arg_resolvers.py 13% 44% +31%
airbyte/mcp/int...c_history_ui.py 0% 36% +36%
airbyte/mcp/int...hared_models.py 0% 81% +81%
airbyte/cloud/models.py 0% 91% +91%
airbyte/mcp/int..._registry_ui.py 0% 92% +92%
airbyte/mcp/int...nc_status_ui.py 0% 97% +97%
airbyte/mcp/_guards.py 0% 100% +100%

Python / code-coverage/pytest

The overall coverage in commit 622c268 in the devin/1784914782-mcp... branch is 73%. The coverage in commit d9f652f in the main branch is 71%.

Show a code coverage summary of the most impacted files.
File main d9f652f devin/1784914782-mcp... 622c268 +/-
airbyte/mcp/_tool_utils.py 72% 84% +12%
airbyte/mcp/registry.py 53% 70% +17%
airbyte/mcp/server.py 69% 88% +19%
airbyte/mcp/_arg_resolvers.py 13% 44% +31%
airbyte/mcp/int...c_history_ui.py 0% 36% +36%
airbyte/mcp/int...hared_models.py 0% 81% +81%
airbyte/mcp/int..._registry_ui.py 0% 92% +92%
airbyte/cloud/models.py 0% 93% +93%
airbyte/mcp/int...nc_status_ui.py 0% 97% +97%
airbyte/mcp/_guards.py 0% 100% +100%

Updated July 24, 2026 23:08 UTC

Co-Authored-By: AJ Steers <aj@airbyte.io>
Copilot AI review requested due to automatic review settings July 24, 2026 17:57

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.

Comment thread airbyte/mcp/cloud.py Outdated
Comment thread airbyte/mcp/cloud.py
Comment thread airbyte/mcp/_tool_utils.py Outdated
Copilot AI review requested due to automatic review settings July 24, 2026 18:29

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.

Comment thread airbyte/mcp/_tool_utils.py Outdated
Comment thread airbyte/cloud/_credentials.py Outdated
Copilot AI review requested due to automatic review settings July 24, 2026 18:38

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

tests/unit_tests/test_mcp_tool_utils.py:108

  • Same as above: prefer patching the airbyte.mcp._tool_utils.is_hosted_mcp_mode callable over mutating the private meta._HOSTED_MCP_MODE_ENABLED variable (and remove the now-unused meta import if you make this change).
    with patch.object(meta, "_HOSTED_MCP_MODE_ENABLED", False):

Comment thread tests/unit_tests/test_mcp_tool_utils.py Outdated
Co-Authored-By: AJ Steers <aj@airbyte.io>
Copilot AI review requested due to automatic review settings July 24, 2026 21:44

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated no new comments.

Co-Authored-By: AJ Steers <aj@airbyte.io>
Copilot AI review requested due to automatic review settings July 24, 2026 22:13

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated no new comments.

organizations.py and workspaces.py imported the _AirbyteCredentials name
directly, which CodeQL flags as py/unsafe-cyclic-import (the name may be
undefined mid-cycle). Import the _credentials module instead so the class
is resolved lazily at use time, downgrading the alert to a benign
module-level cyclic import.

Co-Authored-By: AJ Steers <aj@airbyte.io>
Copilot AI review requested due to automatic review settings July 24, 2026 22:28
Comment thread airbyte/exceptions.py Fixed
Comment thread airbyte/cloud/organizations.py Fixed
Comment thread airbyte/cloud/workspaces.py Fixed

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.

Comment thread airbyte/cloud/_credentials.py
Copilot AI review requested due to automatic review settings July 24, 2026 22:34

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

airbyte/exceptions.py:541

  • The workspace_url property assumes workspace has a .workspace_url attribute; typing it as Any removes useful type checking. Consider annotating it with the local _WorkspaceWithUrl protocol (added above) so type checkers can validate callers.
    workspace: Any | None = None

Comment thread airbyte/exceptions.py Outdated
AirbyteError.workspace was downgraded to Any when its CloudWorkspace import
was removed, losing static checking of the workspace_url property. Declare a
local _WorkspaceWithUrl Protocol that structurally describes the one attribute
exceptions needs, so the field is properly typed without importing
airbyte.cloud (duck typing, no shared ancestor required).

Co-Authored-By: AJ Steers <aj@airbyte.io>
Copilot AI review requested due to automatic review settings July 24, 2026 22:42
Comment thread airbyte/exceptions.py Fixed
Comment thread airbyte/exceptions.py Fixed

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated no new comments.

Copilot AI review requested due to automatic review settings July 24, 2026 22:46

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated no new comments.

@aaronsteers
Aaron ("AJ") Steers (aaronsteers) merged commit 7aff5b4 into main Jul 24, 2026
23 checks passed
@aaronsteers
Aaron ("AJ") Steers (aaronsteers) deleted the devin/1784914782-mcp-mode-aware-cred-guidance branch July 24, 2026 23:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants