From 6d8bb979bb55787c77530aa85ebeeda1426a647d Mon Sep 17 00:00:00 2001 From: GWeale Date: Mon, 24 Aug 2026 22:29:39 +0000 Subject: [PATCH] fix(integrations): send api registry credentials only to google api endpoints (v1) ApiRegistry attaches the caller's ADC bearer token to whatever host a registry entry names, so a non-Google MCP server registered in the project receives the caller's credentials. Scope the header to Google API endpoints; other servers get theirs from header_provider. Carries the prerequisite mTLS commit, which moves the registry fetch from httpx to AuthorizedSession so a client certificate can be presented, and adds the two _mtls_utils members it needs. --- .../integrations/api_registry/api_registry.py | 76 ++++- src/google/adk/utils/_mtls_utils.py | 41 +++ .../api_registry/test_api_registry.py | 273 +++++++++++------- 3 files changed, 270 insertions(+), 120 deletions(-) create mode 100644 src/google/adk/utils/_mtls_utils.py diff --git a/src/google/adk/integrations/api_registry/api_registry.py b/src/google/adk/integrations/api_registry/api_registry.py index 966ad68b7d8..19678f240dd 100644 --- a/src/google/adk/integrations/api_registry/api_registry.py +++ b/src/google/adk/integrations/api_registry/api_registry.py @@ -14,18 +14,51 @@ from __future__ import annotations +import os from typing import Any from typing import Callable +from urllib.parse import urlparse from google.adk.agents.readonly_context import ReadonlyContext from google.adk.tools.base_toolset import ToolPredicate from google.adk.tools.mcp_tool.mcp_session_manager import StreamableHTTPConnectionParams from google.adk.tools.mcp_tool.mcp_toolset import McpToolset +from google.adk.utils import _mtls_utils import google.auth -import google.auth.transport.requests -import httpx +from google.auth.transport import mtls +from google.auth.transport import requests as requests_auth +import requests API_REGISTRY_URL = "https://cloudapiregistry.googleapis.com" +API_REGISTRY_MTLS_URL = "https://cloudapiregistry.mtls.googleapis.com" + + +def _get_api_registry_url(client_cert_source: Any | None = None) -> str: + """Returns the base URL based on mTLS configuration and cert availability.""" + use_mtls_endpoint_str = os.getenv( + "GOOGLE_API_USE_MTLS_ENDPOINT", _mtls_utils.MtlsEndpoint.AUTO.value + ).lower() + try: + use_mtls_endpoint = _mtls_utils.MtlsEndpoint(use_mtls_endpoint_str) + except ValueError: + use_mtls_endpoint = _mtls_utils.MtlsEndpoint.AUTO + if (use_mtls_endpoint is _mtls_utils.MtlsEndpoint.ALWAYS) or ( + use_mtls_endpoint is _mtls_utils.MtlsEndpoint.AUTO + and client_cert_source is not None + ): + return API_REGISTRY_MTLS_URL + return API_REGISTRY_URL + + +def _is_google_api(url: str) -> bool: + """Checks if the given URL points to a Google API endpoint over https.""" + parsed_url = urlparse(url) + if parsed_url.scheme != "https" or not parsed_url.hostname: + return False + return ( + parsed_url.hostname == "googleapis.com" + or parsed_url.hostname.endswith(".googleapis.com") + ) class ApiRegistry: @@ -53,19 +86,38 @@ def __init__( self._mcp_servers: dict[str, dict[str, Any]] = {} self._header_provider = header_provider - url = f"{API_REGISTRY_URL}/v1beta/projects/{self.api_registry_project_id}/locations/{self.location}/mcpServers" + use_client_cert = _mtls_utils.use_client_cert_effective() + client_cert_source = None + if use_client_cert: + client_cert_source = ( + mtls.default_client_cert_source() + if mtls.has_default_client_cert_source() + else None + ) + base_url = _get_api_registry_url(client_cert_source) + + url = f"{base_url}/v1beta/projects/{self.api_registry_project_id}/locations/{self.location}/mcpServers" try: - headers = self._get_auth_headers() - headers["Content-Type"] = "application/json" + quota_project_id = getattr(self._credentials, "quota_project_id", None) + headers = { + "Content-Type": "application/json", + } + if quota_project_id: + headers["x-goog-user-project"] = quota_project_id + page_token = None - with httpx.Client() as client: + with requests_auth.AuthorizedSession( + credentials=self._credentials + ) as session: + if use_client_cert: + session.configure_mtls_channel(client_cert_source) while True: params = {} if page_token: params["pageToken"] = page_token - response = client.get(url, headers=headers, params=params) + response = session.get(url, headers=headers, params=params) response.raise_for_status() data = response.json() mcp_servers_list = data.get("mcpServers", []) @@ -77,7 +129,7 @@ def __init__( page_token = data.get("nextPageToken") if not page_token: break - except (httpx.HTTPError, ValueError) as e: + except (requests.exceptions.RequestException, ValueError) as e: # Handle error in fetching or parsing tool definitions raise RuntimeError( f"Error fetching MCP servers from API Registry: {e}" @@ -110,12 +162,18 @@ def get_toolset( raise ValueError(f"MCP server {mcp_server_name} has no URLs.") mcp_server_url = server["urls"][0] - headers = self._get_auth_headers() # Only prepend "https://" if the URL doesn't already have a scheme if not mcp_server_url.startswith(("http://", "https://")): mcp_server_url = "https://" + mcp_server_url + # A registry entry can name any host, so the caller's own credentials are + # only attached to Google API endpoints. Other servers get their headers + # from the header_provider. + headers = ( + self._get_auth_headers() if _is_google_api(mcp_server_url) else None + ) + return McpToolset( connection_params=StreamableHTTPConnectionParams( url=mcp_server_url, diff --git a/src/google/adk/utils/_mtls_utils.py b/src/google/adk/utils/_mtls_utils.py new file mode 100644 index 00000000000..cd8deda30d5 --- /dev/null +++ b/src/google/adk/utils/_mtls_utils.py @@ -0,0 +1,41 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Utilities for mTLS regional endpoint resolution.""" + +from __future__ import annotations + +import enum +import os + +from google.auth.transport import mtls + + +class MtlsEndpoint(enum.Enum): + """Enum for the mTLS endpoint setting.""" + + AUTO = "auto" + ALWAYS = "always" + NEVER = "never" + + +def use_client_cert_effective() -> bool: + """Returns whether client certificate should be used for mTLS.""" + try: + return bool(mtls.should_use_client_cert()) + except (ImportError, AttributeError): + return ( + os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false").lower() + == "true" + ) diff --git a/tests/unittests/integrations/api_registry/test_api_registry.py b/tests/unittests/integrations/api_registry/test_api_registry.py index 7edaee9fecf..7e29de6f534 100644 --- a/tests/unittests/integrations/api_registry/test_api_registry.py +++ b/tests/unittests/integrations/api_registry/test_api_registry.py @@ -12,16 +12,15 @@ # See the License for the specific language governing permissions and # limitations under the License. -import sys +import os import unittest -from unittest.mock import create_autospec from unittest.mock import MagicMock from unittest.mock import patch from google.adk.integrations import api_registry from google.adk.integrations.api_registry import ApiRegistry from google.adk.tools.mcp_tool.mcp_session_manager import StreamableHTTPConnectionParams -import httpx +import requests MOCK_MCP_SERVERS_LIST = { "mcpServers": [ @@ -44,6 +43,14 @@ "name": "test-mcp-server-https", "urls": ["https://mcp.server_https.com"], }, + { + "name": "test-mcp-server-google", + "urls": ["mcp.us-central1.googleapis.com"], + }, + { + "name": "test-mcp-server-google-http", + "urls": ["http://mcp.us-central1.googleapis.com"], + }, ] } @@ -66,66 +73,68 @@ def setUp(self): mock_auth_patcher.start() self.addCleanup(mock_auth_patcher.stop) - @patch("httpx.Client", autospec=True) - def test_init_success(self, MockHttpClient): + mock_session_patcher = patch( + "google.auth.transport.requests.AuthorizedSession", + autospec=True, + ) + self.mock_session_class = mock_session_patcher.start() + self.mock_session = self.mock_session_class.return_value + self.mock_session.__enter__.return_value = self.mock_session + self.addCleanup(mock_session_patcher.stop) + + mock_use_cert_patcher = patch( + "google.adk.integrations.api_registry.api_registry._mtls_utils.use_client_cert_effective", + return_value=False, + ) + mock_use_cert_patcher.start() + self.addCleanup(mock_use_cert_patcher.stop) + + def test_init_success(self): mock_response = MagicMock() mock_response.raise_for_status = MagicMock() mock_response.json = MagicMock(return_value=MOCK_MCP_SERVERS_LIST) - mock_client_instance = MockHttpClient.return_value - mock_client_instance.__enter__.return_value = mock_client_instance - mock_client_instance.get.return_value = mock_response + self.mock_session.get.return_value = mock_response - api_registry = ApiRegistry( + api_registry_instance = ApiRegistry( api_registry_project_id=self.project_id, location=self.location ) - self.assertEqual(len(api_registry._mcp_servers), 5) - self.assertIn("test-mcp-server-1", api_registry._mcp_servers) - self.assertIn("test-mcp-server-2", api_registry._mcp_servers) - self.assertIn("test-mcp-server-no-url", api_registry._mcp_servers) - self.assertIn("test-mcp-server-http", api_registry._mcp_servers) - self.assertIn("test-mcp-server-https", api_registry._mcp_servers) - mock_client_instance.get.assert_called_once_with( + self.assertEqual(len(api_registry_instance._mcp_servers), 7) + self.assertIn("test-mcp-server-1", api_registry_instance._mcp_servers) + self.assertIn("test-mcp-server-2", api_registry_instance._mcp_servers) + self.assertIn("test-mcp-server-no-url", api_registry_instance._mcp_servers) + self.assertIn("test-mcp-server-http", api_registry_instance._mcp_servers) + self.assertIn("test-mcp-server-https", api_registry_instance._mcp_servers) + self.mock_session.get.assert_called_once_with( f"https://cloudapiregistry.googleapis.com/v1beta/projects/{self.project_id}/locations/{self.location}/mcpServers", headers={ - "Authorization": "Bearer mock_token", "Content-Type": "application/json", }, params={}, ) - @patch("httpx.Client", autospec=True) - def test_init_with_quota_project_id_success(self, MockHttpClient): + def test_init_with_quota_project_id_success(self): self.mock_credentials.quota_project_id = "quota-project" - mock_response = create_autospec(httpx.Response, instance=True) + mock_response = MagicMock() mock_response.json.return_value = MOCK_MCP_SERVERS_LIST - mock_client_instance = MockHttpClient.return_value - mock_client_instance.__enter__.return_value = mock_client_instance - mock_client_instance.get.return_value = mock_response + self.mock_session.get.return_value = mock_response - api_registry = ApiRegistry( + api_registry_instance = ApiRegistry( api_registry_project_id=self.project_id, location=self.location ) - self.assertEqual(len(api_registry._mcp_servers), 5) - self.assertIn("test-mcp-server-1", api_registry._mcp_servers) - self.assertIn("test-mcp-server-2", api_registry._mcp_servers) - self.assertIn("test-mcp-server-no-url", api_registry._mcp_servers) - self.assertIn("test-mcp-server-http", api_registry._mcp_servers) - self.assertIn("test-mcp-server-https", api_registry._mcp_servers) - mock_client_instance.get.assert_called_once_with( + self.assertEqual(len(api_registry_instance._mcp_servers), 7) + self.mock_session.get.assert_called_once_with( f"https://cloudapiregistry.googleapis.com/v1beta/projects/{self.project_id}/locations/{self.location}/mcpServers", headers={ - "Authorization": "Bearer mock_token", "Content-Type": "application/json", "x-goog-user-project": "quota-project", }, params={}, ) - @patch("httpx.Client", autospec=True) - def test_init_with_pagination_success(self, MockHttpClient): - mock_response1 = create_autospec(httpx.Response, instance=True) + def test_init_with_pagination_success(self): + mock_response1 = MagicMock() mock_response1.json.return_value = { "mcpServers": [ { @@ -139,7 +148,7 @@ def test_init_with_pagination_success(self, MockHttpClient): ], "nextPageToken": "next_page_token", } - mock_response2 = create_autospec(httpx.Response, instance=True) + mock_response2 = MagicMock() mock_response2.json.return_value = { "mcpServers": [ { @@ -155,43 +164,31 @@ def test_init_with_pagination_success(self, MockHttpClient): }, ] } - mock_client_instance = MockHttpClient.return_value - mock_client_instance.__enter__.return_value = mock_client_instance - mock_client_instance.get.side_effect = [mock_response1, mock_response2] + self.mock_session.get.side_effect = [mock_response1, mock_response2] - api_registry = ApiRegistry( + api_registry_instance = ApiRegistry( api_registry_project_id=self.project_id, location=self.location ) - self.assertEqual(len(api_registry._mcp_servers), 5) - self.assertIn("test-mcp-server-1", api_registry._mcp_servers) - self.assertIn("test-mcp-server-2", api_registry._mcp_servers) - self.assertIn("test-mcp-server-no-url", api_registry._mcp_servers) - self.assertIn("test-mcp-server-http", api_registry._mcp_servers) - self.assertIn("test-mcp-server-https", api_registry._mcp_servers) - self.assertEqual(mock_client_instance.get.call_count, 2) - mock_client_instance.get.assert_any_call( + self.assertEqual(len(api_registry_instance._mcp_servers), 5) + self.assertEqual(self.mock_session.get.call_count, 2) + self.mock_session.get.assert_any_call( f"https://cloudapiregistry.googleapis.com/v1beta/projects/{self.project_id}/locations/{self.location}/mcpServers", headers={ - "Authorization": "Bearer mock_token", "Content-Type": "application/json", }, params={}, ) - mock_client_instance.get.assert_called_with( + self.mock_session.get.assert_called_with( f"https://cloudapiregistry.googleapis.com/v1beta/projects/{self.project_id}/locations/{self.location}/mcpServers", headers={ - "Authorization": "Bearer mock_token", "Content-Type": "application/json", }, params={"pageToken": "next_page_token"}, ) - @patch("httpx.Client", autospec=True) - def test_init_http_error(self, MockHttpClient): - mock_client_instance = MockHttpClient.return_value - mock_client_instance.__enter__.return_value = mock_client_instance - mock_client_instance.get.side_effect = httpx.RequestError( + def test_init_http_error(self): + self.mock_session.get.side_effect = requests.exceptions.RequestException( "Connection failed" ) @@ -200,17 +197,14 @@ def test_init_http_error(self, MockHttpClient): api_registry_project_id=self.project_id, location=self.location ) - @patch("httpx.Client", autospec=True) - def test_init_bad_response(self, MockHttpClient): + def test_init_bad_response(self): mock_response = MagicMock() mock_response.raise_for_status = MagicMock( - side_effect=httpx.HTTPStatusError( + side_effect=requests.exceptions.HTTPError( "Not Found", request=MagicMock(), response=MagicMock() ) ) - mock_client_instance = MockHttpClient.return_value - mock_client_instance.__enter__.return_value = mock_client_instance - mock_client_instance.get.return_value = mock_response + self.mock_session.get.return_value = mock_response with self.assertRaisesRegex(RuntimeError, "Error fetching MCP servers"): ApiRegistry( @@ -222,25 +216,22 @@ def test_init_bad_response(self, MockHttpClient): "google.adk.integrations.api_registry.api_registry.McpToolset", autospec=True, ) - @patch("httpx.Client", autospec=True) - async def test_get_toolset_success(self, MockHttpClient, MockMcpToolset): + def test_get_toolset_success(self, MockMcpToolset): mock_response = MagicMock() mock_response.raise_for_status = MagicMock() mock_response.json = MagicMock(return_value=MOCK_MCP_SERVERS_LIST) - mock_client_instance = MockHttpClient.return_value - mock_client_instance.__enter__.return_value = mock_client_instance - mock_client_instance.get.return_value = mock_response + self.mock_session.get.return_value = mock_response - api_registry = ApiRegistry( + api_registry_instance = ApiRegistry( api_registry_project_id=self.project_id, location=self.location ) - toolset = api_registry.get_toolset("test-mcp-server-1") + toolset = api_registry_instance.get_toolset("test-mcp-server-1") MockMcpToolset.assert_called_once_with( connection_params=StreamableHTTPConnectionParams( url="https://mcp.server1.com", - headers={"Authorization": "Bearer mock_token"}, + headers=None, ), tool_filter=None, tool_name_prefix=None, @@ -252,26 +243,21 @@ async def test_get_toolset_success(self, MockHttpClient, MockMcpToolset): "google.adk.integrations.api_registry.api_registry.McpToolset", autospec=True, ) - @patch("httpx.Client", autospec=True) - async def test_get_toolset_with_quota_project_id_success( - self, MockHttpClient, MockMcpToolset - ): + def test_get_toolset_with_quota_project_id_success(self, MockMcpToolset): self.mock_credentials.quota_project_id = "quota-project" - mock_response = create_autospec(httpx.Response, instance=True) + mock_response = MagicMock() mock_response.json.return_value = MOCK_MCP_SERVERS_LIST - mock_client_instance = MockHttpClient.return_value - mock_client_instance.__enter__.return_value = mock_client_instance - mock_client_instance.get.return_value = mock_response + self.mock_session.get.return_value = mock_response - api_registry = ApiRegistry( + api_registry_instance = ApiRegistry( api_registry_project_id=self.project_id, location=self.location ) - toolset = api_registry.get_toolset("test-mcp-server-1") + toolset = api_registry_instance.get_toolset("test-mcp-server-google") MockMcpToolset.assert_called_once_with( connection_params=StreamableHTTPConnectionParams( - url="https://mcp.server1.com", + url="https://mcp.us-central1.googleapis.com", headers={ "Authorization": "Bearer mock_token", "x-goog-user-project": "quota-project", @@ -287,23 +273,18 @@ async def test_get_toolset_with_quota_project_id_success( "google.adk.integrations.api_registry.api_registry.McpToolset", autospec=True, ) - @patch("httpx.Client", autospec=True) - async def test_get_toolset_with_filter_and_prefix( - self, MockHttpClient, MockMcpToolset - ): + def test_get_toolset_with_filter_and_prefix(self, MockMcpToolset): mock_response = MagicMock() mock_response.raise_for_status = MagicMock() mock_response.json = MagicMock(return_value=MOCK_MCP_SERVERS_LIST) - mock_client_instance = MockHttpClient.return_value - mock_client_instance.__enter__.return_value = mock_client_instance - mock_client_instance.get.return_value = mock_response + self.mock_session.get.return_value = mock_response - api_registry = ApiRegistry( + api_registry_instance = ApiRegistry( api_registry_project_id=self.project_id, location=self.location ) tool_filter = ["tool1"] tool_name_prefix = "prefix_" - toolset = api_registry.get_toolset( + toolset = api_registry_instance.get_toolset( "test-mcp-server-1", tool_filter=tool_filter, tool_name_prefix=tool_name_prefix, @@ -312,7 +293,7 @@ async def test_get_toolset_with_filter_and_prefix( MockMcpToolset.assert_called_once_with( connection_params=StreamableHTTPConnectionParams( url="https://mcp.server1.com", - headers={"Authorization": "Bearer mock_token"}, + headers=None, ), tool_filter=tool_filter, tool_name_prefix=tool_name_prefix, @@ -328,16 +309,13 @@ def test_get_toolset_url_scheme(self): for mock_server_name, mock_url in params: with self.subTest(server_name=mock_server_name): with ( - patch.object(httpx, "Client", autospec=True) as MockHttpClient, patch.object( api_registry.api_registry, "McpToolset", autospec=True ) as MockMcpToolset, ): - mock_response = create_autospec(httpx.Response, instance=True) + mock_response = MagicMock() mock_response.json.return_value = MOCK_MCP_SERVERS_LIST - mock_client_instance = MockHttpClient.return_value - mock_client_instance.__enter__.return_value = mock_client_instance - mock_client_instance.get.return_value = mock_response + self.mock_session.get.return_value = mock_response api_registry_instance = ApiRegistry( api_registry_project_id=self.project_id, location=self.location @@ -348,41 +326,114 @@ def test_get_toolset_url_scheme(self): MockMcpToolset.assert_called_once_with( connection_params=StreamableHTTPConnectionParams( url=mock_url, - headers={"Authorization": "Bearer mock_token"}, + headers=None, ), tool_filter=None, tool_name_prefix=None, header_provider=None, ) - @patch("httpx.Client", autospec=True) - async def test_get_toolset_server_not_found(self, MockHttpClient): + def test_get_toolset_credentials_only_for_google_api_url(self): + params = [ + ("test-mcp-server-1", None), + ("test-mcp-server-http", None), + ("test-mcp-server-https", None), + ("test-mcp-server-google-http", None), + ("test-mcp-server-google", {"Authorization": "Bearer mock_token"}), + ] + for mock_server_name, expected_headers in params: + with self.subTest(server_name=mock_server_name): + with patch.object( + api_registry.api_registry, "McpToolset", autospec=True + ) as MockMcpToolset: + mock_response = MagicMock() + mock_response.json.return_value = MOCK_MCP_SERVERS_LIST + self.mock_session.get.return_value = mock_response + + api_registry_instance = ApiRegistry( + api_registry_project_id=self.project_id, location=self.location + ) + + api_registry_instance.get_toolset(mock_server_name) + + connection_params = MockMcpToolset.call_args.kwargs[ + "connection_params" + ] + self.assertEqual(connection_params.headers, expected_headers) + + def test_get_toolset_server_not_found(self): mock_response = MagicMock() mock_response.raise_for_status = MagicMock() mock_response.json = MagicMock(return_value=MOCK_MCP_SERVERS_LIST) - mock_client_instance = MockHttpClient.return_value - mock_client_instance.__enter__.return_value = mock_client_instance - mock_client_instance.get.return_value = mock_response + self.mock_session.get.return_value = mock_response - api_registry = ApiRegistry( + api_registry_instance = ApiRegistry( api_registry_project_id=self.project_id, location=self.location ) with self.assertRaisesRegex(ValueError, "not found in API Registry"): - api_registry.get_toolset("non-existent-server") + api_registry_instance.get_toolset("non-existent-server") - @patch("httpx.Client", autospec=True) - async def test_get_toolset_server_no_url(self, MockHttpClient): + def test_get_toolset_server_no_url(self): mock_response = MagicMock() mock_response.raise_for_status = MagicMock() mock_response.json = MagicMock(return_value=MOCK_MCP_SERVERS_LIST) - mock_client_instance = MockHttpClient.return_value - mock_client_instance.__enter__.return_value = mock_client_instance - mock_client_instance.get.return_value = mock_response + self.mock_session.get.return_value = mock_response - api_registry = ApiRegistry( + api_registry_instance = ApiRegistry( api_registry_project_id=self.project_id, location=self.location ) with self.assertRaisesRegex(ValueError, "has no URLs"): - api_registry.get_toolset("test-mcp-server-no-url") + api_registry_instance.get_toolset("test-mcp-server-no-url") + + +class TestApiRegistryMtls(unittest.IsolatedAsyncioTestCase): + + def setUp(self): + self.project_id = "test-project" + self.location = "global" + self.mock_credentials = MagicMock() + self.mock_credentials.token = "mock_token" + self.mock_credentials.refresh = MagicMock() + self.mock_credentials.quota_project_id = None + mock_auth_patcher = patch( + "google.auth.default", + return_value=(self.mock_credentials, None), + autospec=True, + ) + mock_auth_patcher.start() + self.addCleanup(mock_auth_patcher.stop) + + @patch( + "google.auth.transport.mtls.has_default_client_cert_source", + return_value=True, + ) + @patch("google.auth.transport.mtls.default_client_cert_source") + @patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"}) + def test_init_configures_mtls(self, mock_cert_source, _mock_has_cert): + mock_cert_source.return_value = lambda: (b"cert", b"key") + with ( + patch( + "google.adk.integrations.api_registry.api_registry._mtls_utils.use_client_cert_effective", + return_value=True, + ), + patch( + "google.auth.transport.requests.AuthorizedSession", + autospec=True, + ) as mock_session_class, + ): + mock_response = MagicMock() + mock_response.raise_for_status = MagicMock() + mock_response.json.return_value = MOCK_MCP_SERVERS_LIST + mock_session = mock_session_class.return_value + mock_session.__enter__.return_value = mock_session + mock_session.get.return_value = mock_response + + _ = ApiRegistry( + api_registry_project_id=self.project_id, location=self.location + ) + + mock_session.configure_mtls_channel.assert_called_once() + args, _ = mock_session.get.call_args + self.assertIn("cloudapiregistry.mtls.googleapis.com", args[0])