Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions airbyte/mcp/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,14 @@
OIDC_CLIENT_SECRET_ENV = "AIRBYTE_MCP_OIDC_CLIENT_SECRET"
OIDC_CONFIG_URL_ENV = "AIRBYTE_MCP_OIDC_CONFIG_URL"

# Space-separated upstream authorize scopes requested for the interactive OIDC
# flow, also advertised to clients via DCR/`.well-known` and enforced on the
# verified upstream token. `openid` is required for OIDC: without it the IdP may
# issue an identity-only token that downstream APIs reject. Defaults to the
# standard OIDC set; a deployment can override for a realm needing other scopes.
OIDC_SCOPES_ENV = "AIRBYTE_MCP_OIDC_SCOPES"
DEFAULT_OIDC_SCOPES = "openid email profile"
Comment thread
aaronsteers marked this conversation as resolved.
Outdated

# Headless JWT verifier. A signing-key source (`JWKS_URI_ENV` or
# `JWT_PUBLIC_KEY_ENV`) activates it; issuer/audience/algorithm refine it.
JWKS_URI_ENV = "AIRBYTE_MCP_AUTH_JWKS_URI"
Expand Down Expand Up @@ -278,6 +286,7 @@ def _create_auth() -> AuthProvider | None:
client_id=oidc_client_id,
client_secret=oidc_client_secret,
base_url=base_url,
required_scopes=_env_or_default(OIDC_SCOPES_ENV, DEFAULT_OIDC_SCOPES).split(),
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
client_storage=_resolve_client_storage(encryption_source_material=oidc_client_secret),
)

Expand Down
19 changes: 19 additions & 0 deletions tests/unit_tests/test_mcp_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
server.OIDC_CLIENT_ID_ENV,
server.OIDC_CLIENT_SECRET_ENV,
server.OIDC_CONFIG_URL_ENV,
server.OIDC_SCOPES_ENV,
server.OIDC_CLIENT_STORAGE_FACTORY_ENV,
server.JWKS_URI_ENV,
server.JWT_PUBLIC_KEY_ENV,
Expand Down Expand Up @@ -185,10 +186,28 @@ def test_create_auth_activates_oidc_when_credentials_present(
assert oidc.client_id == "cid"
assert oidc.client_secret == "csecret"
assert oidc.config_url == "https://idp.example/.well-known"
# `openid` must be requested upstream, else the IdP may issue an
# identity-only token that downstream APIs reject.
assert oidc.required_scopes == ["openid", "email", "profile"]
Comment thread
aaronsteers marked this conversation as resolved.
# No storage factory configured -> in-memory default.
assert oidc.client_storage is None


def test_create_auth_honors_oidc_scopes_override(monkeypatch: MonkeyPatch) -> None:
"""A realm needing different scopes can override the default via env."""
_clear_all_auth_env(monkeypatch)
monkeypatch.setenv(server.OIDC_CLIENT_ID_ENV, "cid")
monkeypatch.setenv(server.OIDC_CLIENT_SECRET_ENV, "csecret")
monkeypatch.setenv(server.OIDC_CONFIG_URL_ENV, "https://idp.example/.well-known")
monkeypatch.setenv(server.OIDC_SCOPES_ENV, "openid custom-api")
captured = _capture_build_mcp_auth(monkeypatch)
server._create_auth()

oidc = captured["oidc"]
assert isinstance(oidc, OIDCAuthConfig)
assert oidc.required_scopes == ["openid", "custom-api"]


def test_create_auth_oidc_without_config_url_raises(monkeypatch: MonkeyPatch) -> None:
"""OIDC credentials without a discovery URL fail clearly, naming the env var."""
_clear_all_auth_env(monkeypatch)
Expand Down
Loading