Skip to content

Commit 45fb53b

Browse files
wukathcopybara-github
authored andcommitted
chore: Move API registry to the integrations folder
Added a deprecation warning in the old tools/api_registry file. Co-authored-by: Kathy Wu <wukathy@google.com> PiperOrigin-RevId: 878660213
1 parent 34c560e commit 45fb53b

6 files changed

Lines changed: 192 additions & 130 deletions

File tree

contributing/samples/api_registry_agent/agent.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
import os
1616

1717
from google.adk.agents.llm_agent import LlmAgent
18-
from google.adk.tools.api_registry import ApiRegistry
18+
from google.adk.integrations.api_registry import ApiRegistry
1919

2020
# TODO: Fill in with your GCloud project id and MCP server name
2121
PROJECT_ID = "your-google-cloud-project-id"
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Licensed under the Apache License, Version 2.0 (the "License");
2+
# you may not use this file except in compliance with the License.
3+
# You may obtain a copy of the License at
4+
#
5+
# http://www.apache.org/licenses/LICENSE-2.0
6+
#
7+
# Unless required by applicable law or agreed to in writing, software
8+
# distributed under the License is distributed on an "AS IS" BASIS,
9+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
# See the License for the specific language governing permissions and
11+
# limitations under the License.
12+
13+
from .api_registry import ApiRegistry
14+
15+
__all__ = [
16+
'ApiRegistry',
17+
]
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
# Copyright 2026 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from __future__ import annotations
16+
17+
from typing import Any
18+
from typing import Callable
19+
20+
from google.adk.agents.readonly_context import ReadonlyContext
21+
from google.adk.tools.base_toolset import ToolPredicate
22+
from google.adk.tools.mcp_tool.mcp_session_manager import StreamableHTTPConnectionParams
23+
from google.adk.tools.mcp_tool.mcp_toolset import McpToolset
24+
import google.auth
25+
import google.auth.transport.requests
26+
import httpx
27+
28+
API_REGISTRY_URL = "https://cloudapiregistry.googleapis.com"
29+
30+
31+
class ApiRegistry:
32+
"""Registry that provides McpToolsets for MCP servers registered in API Registry."""
33+
34+
def __init__(
35+
self,
36+
api_registry_project_id: str,
37+
location: str = "global",
38+
header_provider: (
39+
Callable[[ReadonlyContext], dict[str, str]] | None
40+
) = None,
41+
):
42+
"""Initialize the API Registry.
43+
44+
Args:
45+
api_registry_project_id: The project ID for the Google Cloud API Registry.
46+
location: The location of the API Registry resources.
47+
header_provider: Optional function to provide additional headers for MCP
48+
server calls.
49+
"""
50+
self.api_registry_project_id = api_registry_project_id
51+
self.location = location
52+
self._credentials, _ = google.auth.default()
53+
self._mcp_servers: dict[str, dict[str, Any]] = {}
54+
self._header_provider = header_provider
55+
56+
url = f"{API_REGISTRY_URL}/v1beta/projects/{self.api_registry_project_id}/locations/{self.location}/mcpServers"
57+
58+
try:
59+
headers = self._get_auth_headers()
60+
headers["Content-Type"] = "application/json"
61+
page_token = None
62+
with httpx.Client() as client:
63+
while True:
64+
params = {}
65+
if page_token:
66+
params["pageToken"] = page_token
67+
68+
response = client.get(url, headers=headers, params=params)
69+
response.raise_for_status()
70+
data = response.json()
71+
mcp_servers_list = data.get("mcpServers", [])
72+
for server in mcp_servers_list:
73+
server_name = server.get("name", "")
74+
if server_name:
75+
self._mcp_servers[server_name] = server
76+
77+
page_token = data.get("nextPageToken")
78+
if not page_token:
79+
break
80+
except (httpx.HTTPError, ValueError) as e:
81+
# Handle error in fetching or parsing tool definitions
82+
raise RuntimeError(
83+
f"Error fetching MCP servers from API Registry: {e}"
84+
) from e
85+
86+
def get_toolset(
87+
self,
88+
mcp_server_name: str,
89+
tool_filter: ToolPredicate | list[str] | None = None,
90+
tool_name_prefix: str | None = None,
91+
) -> McpToolset:
92+
"""Return the MCP Toolset based on the params.
93+
94+
Args:
95+
mcp_server_name: Filter to select the MCP server name to get tools from.
96+
tool_filter: Optional filter to select specific tools. Can be a list of
97+
tool names or a ToolPredicate function.
98+
tool_name_prefix: Optional prefix to prepend to the names of the tools
99+
returned by the toolset.
100+
101+
Returns:
102+
McpToolset: A toolset for the MCP server specified.
103+
"""
104+
server = self._mcp_servers.get(mcp_server_name)
105+
if not server:
106+
raise ValueError(
107+
f"MCP server {mcp_server_name} not found in API Registry."
108+
)
109+
if not server.get("urls"):
110+
raise ValueError(f"MCP server {mcp_server_name} has no URLs.")
111+
112+
mcp_server_url = server["urls"][0]
113+
headers = self._get_auth_headers()
114+
115+
# Only prepend "https://" if the URL doesn't already have a scheme
116+
if not mcp_server_url.startswith(("http://", "https://")):
117+
mcp_server_url = "https://" + mcp_server_url
118+
119+
return McpToolset(
120+
connection_params=StreamableHTTPConnectionParams(
121+
url=mcp_server_url,
122+
headers=headers,
123+
),
124+
tool_filter=tool_filter,
125+
tool_name_prefix=tool_name_prefix,
126+
header_provider=self._header_provider,
127+
)
128+
129+
def _get_auth_headers(self) -> dict[str, str]:
130+
"""Refreshes credentials and returns authorization headers."""
131+
request = google.auth.transport.requests.Request()
132+
self._credentials.refresh(request)
133+
headers = {
134+
"Authorization": f"Bearer {self._credentials.token}",
135+
}
136+
# Add quota project header if available in ADC
137+
quota_project_id = getattr(self._credentials, "quota_project_id", None)
138+
if quota_project_id:
139+
headers["x-goog-user-project"] = quota_project_id
140+
return headers

src/google/adk/tools/api_registry.py

Lines changed: 8 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -14,128 +14,13 @@
1414

1515
from __future__ import annotations
1616

17-
from typing import Any
18-
from typing import Callable
17+
import warnings
1918

20-
from google.adk.agents.readonly_context import ReadonlyContext
21-
import google.auth
22-
import google.auth.transport.requests
23-
import httpx
19+
from google.adk.integrations.api_registry import ApiRegistry
2420

25-
from .base_toolset import ToolPredicate
26-
from .mcp_tool.mcp_session_manager import StreamableHTTPConnectionParams
27-
from .mcp_tool.mcp_toolset import McpToolset
28-
29-
API_REGISTRY_URL = "https://cloudapiregistry.googleapis.com"
30-
31-
32-
class ApiRegistry:
33-
"""Registry that provides McpToolsets for MCP servers registered in API Registry."""
34-
35-
def __init__(
36-
self,
37-
api_registry_project_id: str,
38-
location: str = "global",
39-
header_provider: (
40-
Callable[[ReadonlyContext], dict[str, str]] | None
41-
) = None,
42-
):
43-
"""Initialize the API Registry.
44-
45-
Args:
46-
api_registry_project_id: The project ID for the Google Cloud API Registry.
47-
location: The location of the API Registry resources.
48-
header_provider: Optional function to provide additional headers for MCP
49-
server calls.
50-
"""
51-
self.api_registry_project_id = api_registry_project_id
52-
self.location = location
53-
self._credentials, _ = google.auth.default()
54-
self._mcp_servers: dict[str, dict[str, Any]] = {}
55-
self._header_provider = header_provider
56-
57-
url = f"{API_REGISTRY_URL}/v1beta/projects/{self.api_registry_project_id}/locations/{self.location}/mcpServers"
58-
59-
try:
60-
headers = self._get_auth_headers()
61-
headers["Content-Type"] = "application/json"
62-
page_token = None
63-
with httpx.Client() as client:
64-
while True:
65-
params = {}
66-
if page_token:
67-
params["pageToken"] = page_token
68-
69-
response = client.get(url, headers=headers, params=params)
70-
response.raise_for_status()
71-
data = response.json()
72-
mcp_servers_list = data.get("mcpServers", [])
73-
for server in mcp_servers_list:
74-
server_name = server.get("name", "")
75-
if server_name:
76-
self._mcp_servers[server_name] = server
77-
78-
page_token = data.get("nextPageToken")
79-
if not page_token:
80-
break
81-
except (httpx.HTTPError, ValueError) as e:
82-
# Handle error in fetching or parsing tool definitions
83-
raise RuntimeError(
84-
f"Error fetching MCP servers from API Registry: {e}"
85-
) from e
86-
87-
def get_toolset(
88-
self,
89-
mcp_server_name: str,
90-
tool_filter: ToolPredicate | list[str] | None = None,
91-
tool_name_prefix: str | None = None,
92-
) -> McpToolset:
93-
"""Return the MCP Toolset based on the params.
94-
95-
Args:
96-
mcp_server_name: Filter to select the MCP server name to get tools from.
97-
tool_filter: Optional filter to select specific tools. Can be a list of
98-
tool names or a ToolPredicate function.
99-
tool_name_prefix: Optional prefix to prepend to the names of the tools
100-
returned by the toolset.
101-
102-
Returns:
103-
McpToolset: A toolset for the MCP server specified.
104-
"""
105-
server = self._mcp_servers.get(mcp_server_name)
106-
if not server:
107-
raise ValueError(
108-
f"MCP server {mcp_server_name} not found in API Registry."
109-
)
110-
if not server.get("urls"):
111-
raise ValueError(f"MCP server {mcp_server_name} has no URLs.")
112-
113-
mcp_server_url = server["urls"][0]
114-
headers = self._get_auth_headers()
115-
116-
# Only prepend "https://" if the URL doesn't already have a scheme
117-
if not mcp_server_url.startswith(("http://", "https://")):
118-
mcp_server_url = "https://" + mcp_server_url
119-
120-
return McpToolset(
121-
connection_params=StreamableHTTPConnectionParams(
122-
url=mcp_server_url,
123-
headers=headers,
124-
),
125-
tool_filter=tool_filter,
126-
tool_name_prefix=tool_name_prefix,
127-
header_provider=self._header_provider,
128-
)
129-
130-
def _get_auth_headers(self) -> dict[str, str]:
131-
"""Refreshes credentials and returns authorization headers."""
132-
request = google.auth.transport.requests.Request()
133-
self._credentials.refresh(request)
134-
headers = {
135-
"Authorization": f"Bearer {self._credentials.token}",
136-
}
137-
# Add quota project header if available in ADC
138-
quota_project_id = getattr(self._credentials, "quota_project_id", None)
139-
if quota_project_id:
140-
headers["x-goog-user-project"] = quota_project_id
141-
return headers
21+
warnings.warn(
22+
"google.adk.tools.api_registry is moved to"
23+
" google.adk.integrations.api_registry",
24+
DeprecationWarning,
25+
stacklevel=2,
26+
)
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Licensed under the Apache License, Version 2.0 (the "License");
2+
# you may not use this file except in compliance with the License.
3+
# You may obtain a copy of the License at
4+
#
5+
# http://www.apache.org/licenses/LICENSE-2.0
6+
#
7+
# Unless required by applicable law or agreed to in writing, software
8+
# distributed under the License is distributed on an "AS IS" BASIS,
9+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
# See the License for the specific language governing permissions and
11+
# limitations under the License.

tests/unittests/tools/test_api_registry.py renamed to tests/unittests/integrations/api_registry/test_api_registry.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@
1818
from unittest.mock import MagicMock
1919
from unittest.mock import patch
2020

21-
from google.adk.tools import api_registry
22-
from google.adk.tools.api_registry import ApiRegistry
21+
from google.adk.integrations import api_registry
22+
from google.adk.integrations.api_registry import ApiRegistry
2323
from google.adk.tools.mcp_tool.mcp_session_manager import StreamableHTTPConnectionParams
2424
import httpx
2525

@@ -218,7 +218,10 @@ def test_init_bad_response(self, MockHttpClient):
218218
)
219219
mock_response.raise_for_status.assert_called_once()
220220

221-
@patch("google.adk.tools.api_registry.McpToolset", autospec=True)
221+
@patch(
222+
"google.adk.integrations.api_registry.api_registry.McpToolset",
223+
autospec=True,
224+
)
222225
@patch("httpx.Client", autospec=True)
223226
async def test_get_toolset_success(self, MockHttpClient, MockMcpToolset):
224227
mock_response = MagicMock()
@@ -245,7 +248,10 @@ async def test_get_toolset_success(self, MockHttpClient, MockMcpToolset):
245248
)
246249
self.assertEqual(toolset, MockMcpToolset.return_value)
247250

248-
@patch("google.adk.tools.api_registry.McpToolset", autospec=True)
251+
@patch(
252+
"google.adk.integrations.api_registry.api_registry.McpToolset",
253+
autospec=True,
254+
)
249255
@patch("httpx.Client", autospec=True)
250256
async def test_get_toolset_with_quota_project_id_success(
251257
self, MockHttpClient, MockMcpToolset
@@ -277,7 +283,10 @@ async def test_get_toolset_with_quota_project_id_success(
277283
)
278284
self.assertEqual(toolset, MockMcpToolset.return_value)
279285

280-
@patch("google.adk.tools.api_registry.McpToolset", autospec=True)
286+
@patch(
287+
"google.adk.integrations.api_registry.api_registry.McpToolset",
288+
autospec=True,
289+
)
281290
@patch("httpx.Client", autospec=True)
282291
async def test_get_toolset_with_filter_and_prefix(
283292
self, MockHttpClient, MockMcpToolset
@@ -321,7 +330,7 @@ def test_get_toolset_url_scheme(self):
321330
with (
322331
patch.object(httpx, "Client", autospec=True) as MockHttpClient,
323332
patch.object(
324-
api_registry, "McpToolset", autospec=True
333+
api_registry.api_registry, "McpToolset", autospec=True
325334
) as MockMcpToolset,
326335
):
327336
mock_response = create_autospec(httpx.Response, instance=True)

0 commit comments

Comments
 (0)