Skip to content

Commit f1ccc0c

Browse files
wukathcopybara-github
authored andcommitted
feat: Support page token in API Registry
API registry uses a nextPageToken for pagination, so we should loop through all the pages by sending the received nextPageToken to subsequent requests until no token is returned. The first page contains 1P MCP servers, and later pages contain 3P customer servers. Co-authored-by: Kathy Wu <wukathy@google.com> PiperOrigin-RevId: 853880449
1 parent 8e41f7f commit f1ccc0c

2 files changed

Lines changed: 85 additions & 8 deletions

File tree

src/google/adk/tools/api_registry.py

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414

1515
from __future__ import annotations
1616

17-
import sys
1817
from typing import Any
1918
from typing import Callable
2019

@@ -56,17 +55,29 @@ def __init__(
5655
self._header_provider = header_provider
5756

5857
url = f"{API_REGISTRY_URL}/v1beta/projects/{self.api_registry_project_id}/locations/{self.location}/mcpServers"
58+
5959
try:
6060
headers = self._get_auth_headers()
6161
headers["Content-Type"] = "application/json"
62+
page_token = None
6263
with httpx.Client() as client:
63-
response = client.get(url, headers=headers)
64-
response.raise_for_status()
65-
mcp_servers_list = response.json().get("mcpServers", [])
66-
for server in mcp_servers_list:
67-
server_name = server.get("name", "")
68-
if server_name:
69-
self._mcp_servers[server_name] = server
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
7081
except (httpx.HTTPError, ValueError) as e:
7182
# Handle error in fetching or parsing tool definitions
7283
raise RuntimeError(

tests/unittests/tools/test_api_registry.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ def test_init_success(self, MockHttpClient):
9191
"Authorization": "Bearer mock_token",
9292
"Content-Type": "application/json",
9393
},
94+
params={},
9495
)
9596

9697
@patch("httpx.Client", autospec=True)
@@ -119,6 +120,71 @@ def test_init_with_quota_project_id_success(self, MockHttpClient):
119120
"Content-Type": "application/json",
120121
"x-goog-user-project": "quota-project",
121122
},
123+
params={},
124+
)
125+
126+
@patch("httpx.Client", autospec=True)
127+
def test_init_with_pagination_success(self, MockHttpClient):
128+
mock_response1 = create_autospec(httpx.Response, instance=True)
129+
mock_response1.json.return_value = {
130+
"mcpServers": [
131+
{
132+
"name": "test-mcp-server-1",
133+
"urls": ["mcp.server1.com"],
134+
},
135+
{
136+
"name": "test-mcp-server-2",
137+
"urls": ["mcp.server2.com"],
138+
},
139+
],
140+
"nextPageToken": "next_page_token",
141+
}
142+
mock_response2 = create_autospec(httpx.Response, instance=True)
143+
mock_response2.json.return_value = {
144+
"mcpServers": [
145+
{
146+
"name": "test-mcp-server-no-url",
147+
},
148+
{
149+
"name": "test-mcp-server-http",
150+
"urls": ["http://mcp.server_http.com"],
151+
},
152+
{
153+
"name": "test-mcp-server-https",
154+
"urls": ["https://mcp.server_https.com"],
155+
},
156+
]
157+
}
158+
mock_client_instance = MockHttpClient.return_value
159+
mock_client_instance.__enter__.return_value = mock_client_instance
160+
mock_client_instance.get.side_effect = [mock_response1, mock_response2]
161+
162+
api_registry = ApiRegistry(
163+
api_registry_project_id=self.project_id, location=self.location
164+
)
165+
166+
self.assertEqual(len(api_registry._mcp_servers), 5)
167+
self.assertIn("test-mcp-server-1", api_registry._mcp_servers)
168+
self.assertIn("test-mcp-server-2", api_registry._mcp_servers)
169+
self.assertIn("test-mcp-server-no-url", api_registry._mcp_servers)
170+
self.assertIn("test-mcp-server-http", api_registry._mcp_servers)
171+
self.assertIn("test-mcp-server-https", api_registry._mcp_servers)
172+
self.assertEqual(mock_client_instance.get.call_count, 2)
173+
mock_client_instance.get.assert_any_call(
174+
f"https://cloudapiregistry.googleapis.com/v1beta/projects/{self.project_id}/locations/{self.location}/mcpServers",
175+
headers={
176+
"Authorization": "Bearer mock_token",
177+
"Content-Type": "application/json",
178+
},
179+
params={},
180+
)
181+
mock_client_instance.get.assert_called_with(
182+
f"https://cloudapiregistry.googleapis.com/v1beta/projects/{self.project_id}/locations/{self.location}/mcpServers",
183+
headers={
184+
"Authorization": "Bearer mock_token",
185+
"Content-Type": "application/json",
186+
},
187+
params={"pageToken": "next_page_token"},
122188
)
123189

124190
@patch("httpx.Client", autospec=True)

0 commit comments

Comments
 (0)