fix(mcp): forward verified upstream token, not proxy reference JWT, to Cloud API - #1081
Conversation
…o Cloud API The downstream Airbyte Cloud bearer was resolved from the raw Authorization header first. Behind FastMCP's OAuthProxy/OIDCProxy that header carries the proxy's self-minted reference JWT (iss/aud = the MCP server), which the Airbyte Cloud API rejects with 401. FastMCP already swaps in the verified upstream Airbyte token and exposes it via get_access_token(); drop the header-first precedence so that verified token is forwarded, keeping the raw header only as a fallback for servers with no transport auth provider. Co-Authored-By: AJ Steers <aj@airbyte.io>
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
👋 Greetings, Airbyte Team Member!Here are some helpful tips and reminders for your convenience. 💡 Show Tips and TricksTesting This PyAirbyte VersionYou 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/1784148484-cloud-mcp-forward-verified-token' pyairbyte --help
# Install PyAirbyte from this branch for development:
pip install 'git+https://github.com/airbytehq/PyAirbyte.git@devin/1784148484-cloud-mcp-forward-verified-token'PR Slash CommandsAirbyte Maintainers can execute the following slash commands on your PR:
📚 Show Repo GuidanceHelpful ResourcesCommunity SupportQuestions? Join the #pyairbyte channel in our Slack workspace. |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughWalkthroughThe MCP bearer-token resolver now prioritizes verified transport access tokens, falls back to normalized ChangesBearer token resolution
Estimated code review effort: 2 (Simple) | ~10 minutes Sequence Diagram(s)sequenceDiagram
participant BEARER_TOKEN_CONFIG_ARG
participant _resolve_transport_bearer_token
participant get_access_token
participant get_http_headers
BEARER_TOKEN_CONFIG_ARG->>_resolve_transport_bearer_token: Resolve default bearer token
_resolve_transport_bearer_token->>get_access_token: Request verified access token
get_access_token-->>_resolve_transport_bearer_token: Return token or empty
_resolve_transport_bearer_token->>get_http_headers: Read Authorization header if needed
get_http_headers-->>_resolve_transport_bearer_token: Return header token or empty
_resolve_transport_bearer_token-->>BEARER_TOKEN_CONFIG_ARG: Return normalized token
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
get_http_headers strips the authorization header by default, so the raw
header fallback never saw a token. Explicitly opt in via
include={"authorization"} and assert the opt-in in the regression test.
Co-Authored-By: AJ Steers <aj@airbyte.io>
Code Coverage OverviewLanguages: Python Python / code-coverage/pytest-fastThe overall coverage in the Show a code coverage summary of the most impacted files.
Python / code-coverage/pytest-no-credsThe overall coverage in the Show a code coverage summary of the most impacted files.
Python / code-coverage/pytestThe overall coverage in the Show a code coverage summary of the most impacted files.
Updated |
Summary
Hosted Cloud MCP (
cloud-mcp) was forwarding the wrong token to the Airbyte Cloud API, causing every authenticated Cloud tool call to401after credential resolution succeeded.cloud-mcpruns FastMCP'sOAuthProxy/OIDCProxy, which brokers the caller's Keycloak → Okta login but then mints its own short-lived reference JWT for the MCP client (iss/aud= the MCP server itself,scope="email profile"). That reference JWT is what lands in the rawAuthorizationheader. The Airbyte Cloud API never issued it, so it rejects it with401.The bug was one of source precedence in how the downstream bearer was resolved.
BEARER_TOKEN_CONFIG_ARGdeclaredhttp_header_key=MCP_BEARER_TOKEN_HEADER, andfastmcp_extensions's resolver checks the HTTP header before thedefault. So the rawAuthorizationheader (the reference JWT) always won over_resolve_transport_bearer_token, which returns the verified token fromget_access_token().FastMCP already does the right thing internally —
OAuthProxy.load_access_token()swaps the reference JWT for the upstream Airbyte access token and exposes it viaget_access_token(). This is the same class of token the Ops Webapp uses successfully against the Cloud public/config APIs. The fix simply forwards that verified token instead of the raw header.BEARER_TOKEN_CONFIG_ARG = MCPServerConfigArg( name=MCP_CONFIG_BEARER_TOKEN, - http_header_key=MCP_BEARER_TOKEN_HEADER, # raw header (reference JWT) won → 401 env_var=CLOUD_BEARER_TOKEN_ENV_VAR, normalize_fn=_normalize_bearer_token, default=_resolve_transport_bearer_token, # now the primary source ... )Precedence after this change: explicit
AIRBYTE_CLOUD_BEARER_TOKENenv override → verified transport token (get_access_token) → rawAuthorizationheader fallback → empty (falls through to client-credentials auth for stdio/local).Why the verified token is the right one (source trace)
OIDCProxy._get_verification_token()returnsupstream_token_set.access_token.JWTVerifier.verify_token(token)returnsAccessToken(token=token, ...).get_access_token().tokenis the upstream Airbyte access token, not the proxy's minted reference JWT.Test Plan
Added
tests/unit_tests/test_mcp_tool_utils.py::test_resolve_transport_bearer_token(parametrized):Authorizationheader present → returns the verified token (regression guard for the401)Bearer <tok>header → returns<tok>""Local checks (all green):
uv run --no-sync pytest tests/unit_tests/test_mcp_tool_utils.py→ 11 passeduv run --no-sync ruff format/ruff check(changed files) → cleanuv run --no-sync pyrefly check airbyte/mcp/_tool_utils.py→ 0 errorsLive end-to-end confirmation (verified upstream token accepted by the Cloud API) will come from a hosted retest once this deploys, or a local OAuth round trip — the source trace above establishes the correct token now flows.
Requested by AJ Steers (Aaron ("AJ") Steers (@aaronsteers)).
Link to Devin session: https://app.devin.ai/sessions/9274bd4f3e0a4b0880795fc1ef9c806b
Summary by CodeRabbit
Authorizationheader (case-insensitive lookup).Bearerprefix, including correct empty-token behavior.