diff --git a/airbyte/cloud/client.py b/airbyte/cloud/client.py index 9f83eaf6c..8183e7743 100644 --- a/airbyte/cloud/client.py +++ b/airbyte/cloud/client.py @@ -917,12 +917,7 @@ def get_default_context_for_user(self) -> CloudDefaultContextInfo: "discover workspaces." ) if user is not None and not isinstance(user.get("defaultWorkspaceId"), str): - discovery_hints.append( - "No default workspace is set. Use " - "set_default_cloud_workspace(user_email=, " - "workspace_id=) to durably set one; it applies to both MCP " - "sessions and the Airbyte Cloud web app." - ) + discovery_hints.append(self._missing_default_workspace_hint()) return CloudDefaultContextInfo( user_id=user_id, user_name=user_name, @@ -931,7 +926,9 @@ def get_default_context_for_user(self) -> CloudDefaultContextInfo: default_workspace_name=( default_workspace_info.name if default_workspace_info else None ), - default_workspace_verified=default_workspace_info is not None, + default_workspace_verified=( + default_workspace_info is not None if default_workspace_id is not None else None + ), default_organization_id=( default_workspace_organization.organization_id if default_workspace_organization is not None @@ -952,6 +949,24 @@ def get_default_context_for_user(self) -> CloudDefaultContextInfo: discovery_hints=discovery_hints, ) + def _missing_default_workspace_hint(self) -> str: + """Hint text when the user's profile has no stored default workspace.""" + if self.default_workspace_id is not None: + return ( + "Your Airbyte user profile has no default workspace; this " + "session uses the configured workspace " + f"{self.default_workspace_id}. Use " + "set_default_cloud_workspace(user_email=, " + "workspace_id=) to set a durable default; it applies to " + "both MCP sessions and the Airbyte Cloud web app." + ) + return ( + "No default workspace is set. Use " + "set_default_cloud_workspace(user_email=, " + "workspace_id=) to durably set one; it applies to both MCP " + "sessions and the Airbyte Cloud web app." + ) + def set_default_workspace_for_user( self, *, diff --git a/airbyte/cloud/models.py b/airbyte/cloud/models.py index 995e38d45..59caccd83 100644 --- a/airbyte/cloud/models.py +++ b/airbyte/cloud/models.py @@ -283,8 +283,12 @@ class CloudDefaultContextInfo(BaseModel): default_workspace_name: str | None """The resolved default workspace name, if available.""" - default_workspace_verified: bool - """Whether the resolved default workspace was verified as accessible.""" + default_workspace_verified: bool | None + """Whether the resolved default workspace was verified as accessible. + + `None` when no default workspace ID was resolved, since there is nothing + to verify. + """ unvalidated_workspace_count: int = 0 """Number of direct workspace grants not validated due to the validation cap.""" diff --git a/airbyte/mcp/cloud.py b/airbyte/mcp/cloud.py index 05124c5f2..290edaad2 100644 --- a/airbyte/mcp/cloud.py +++ b/airbyte/mcp/cloud.py @@ -405,8 +405,12 @@ class CloudDefaultContextResult(BaseModel): default_workspace_name: str | None """The resolved default workspace name, if available.""" - default_workspace_verified: bool - """Whether the resolved default workspace was verified as accessible.""" + default_workspace_verified: bool | None + """Whether the resolved default workspace was verified as accessible. + + `None` when no default workspace ID was resolved, since there is nothing + to verify. + """ unvalidated_workspace_count: int = 0 """Number of direct workspace grants not validated due to the validation cap.""" diff --git a/tests/unit_tests/test_cloud_client.py b/tests/unit_tests/test_cloud_client.py index 485ca0f95..0c6c172e2 100644 --- a/tests/unit_tests/test_cloud_client.py +++ b/tests/unit_tests/test_cloud_client.py @@ -1158,6 +1158,57 @@ def test_get_default_context_for_user_hints_setter_when_stored_default_missing() ] +def test_get_default_context_for_user_verified_is_none_without_default() -> None: + """With no resolvable default, `verified` is None rather than False.""" + patches = _api_patches(user={"userId": "user-id"}) + with patches[0], patches[1], patches[2], patches[3], patches[4]: + context = CloudClient(bearer_token="token").get_default_context_for_user() + + assert context.default_workspace_id is None + assert context.default_workspace_verified is None + assert context.discovery_hints == [ + "No default workspace is set. Use " + "set_default_cloud_workspace(user_email=, " + "workspace_id=) to durably set one; it applies to both MCP " + "sessions and the Airbyte Cloud web app." + ] + + +def test_get_default_context_for_user_configured_default_hint() -> None: + """A configured default without a profile default gets an accurate hint.""" + patches = _api_patches(user={"userId": "user-id"}) + with ( + patches[0], + patches[1], + patches[2], + patches[3], + patches[4], + patch( + "airbyte._util.api_util.get_workspace", + return_value=models.WorkspaceResponse( + data_residency="auto", + name="Configured", + notifications=models.NotificationsConfig(), + workspace_id="configured-workspace", + ), + ), + ): + context = CloudClient( + bearer_token="token", + workspace_id="configured-workspace", + ).get_default_context_for_user() + + assert context.default_workspace_id == "configured-workspace" + assert context.default_workspace_verified is True + assert context.discovery_hints == [ + "Your Airbyte user profile has no default workspace; this session " + "uses the configured workspace configured-workspace. Use " + "set_default_cloud_workspace(user_email=, " + "workspace_id=) to set a durable default; it applies to " + "both MCP sessions and the Airbyte Cloud web app." + ] + + def test_get_default_context_for_user_omits_setter_hint_when_default_stored() -> None: patches = _api_patches( user={"userId": "user-id", "defaultWorkspaceId": "workspace-1"},